diff --git a/CHANGELOG.md b/CHANGELOG.md index 145d1b14..abceeda3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,38 @@ All major and minor version changes will be documented in this file. Details of ## Details +### [10.15.0] - 2024-04-02 +- Fix Ciphersaber2 key concatenation [@zb3] | [#1765] +- Fix DeriveEVPKey's array parsing [@zb3] | [#1767] +- Fix JWT operations [@a3957273] | [#1769] +- Added 'Parse Certificate Signing Request' operation [@jkataja] | [#1504] +- Added 'Extract Hash Values' operation [@MShwed] | [#512] +- Added 'DateTime Delta' operation [@tomgond] | [#1732] + +### [10.14.0] - 2024-03-31 +- Added 'To Float' and 'From Float' operations [@tcode2k16] | [#1762] +- Fix ChaCha raw export option [@joostrijneveld] | [#1606] +- Update x86 disassembler vendor library [@evanreichard] | [#1197] +- Allow variable Blowfish key sizes [@cbeuw] | [#933] +- Added 'XXTEA' operation [@devcydo] | [#1361] + +### [10.13.0] - 2024-03-30 +- Added 'FangURL' operation [@breakersall] [@arnydo] | [#1591] [#654] + +### [10.12.0] - 2024-03-29 +- Added 'Salsa20' and 'XSalsa20' operation [@joostrijneveld] | [#1750] + +### [10.11.0] - 2024-03-29 +- Add HEIC/HEIF file signatures [@simonw] | [#1757] +- Update xmldom to fix medium security vulnerability [@chriswhite199] | [#1752] +- Update JSONWebToken to fix medium security vulnerability [@chriswhite199] | [#1753] + +### [10.10.0] - 2024-03-27 +- Added 'JA4 Fingerprint' operation [@n1474335] | [#1759] + +### [10.9.0] - 2024-03-26 +- Line ending sequences and UTF-8 character encoding are now detected automatically [@n1474335] | [65ffd8d] + ### [10.8.0] - 2024-02-13 - Add official Docker images [@AshCorr] | [#1699] @@ -386,6 +418,13 @@ All major and minor version changes will be documented in this file. Details of ## [4.0.0] - 2016-11-28 - Initial open source commit [@n1474335] | [b1d73a72](https://github.com/gchq/CyberChef/commit/b1d73a725dc7ab9fb7eb789296efd2b7e4b08306) +[10.15.0]: https://github.com/gchq/CyberChef/releases/tag/v10.15.0 +[10.14.0]: https://github.com/gchq/CyberChef/releases/tag/v10.14.0 +[10.13.0]: https://github.com/gchq/CyberChef/releases/tag/v10.13.0 +[10.12.0]: https://github.com/gchq/CyberChef/releases/tag/v10.12.0 +[10.11.0]: https://github.com/gchq/CyberChef/releases/tag/v10.11.0 +[10.10.0]: https://github.com/gchq/CyberChef/releases/tag/v10.10.0 +[10.9.0]: https://github.com/gchq/CyberChef/releases/tag/v10.9.0 [10.8.0]: https://github.com/gchq/CyberChef/releases/tag/v10.7.0 [10.7.0]: https://github.com/gchq/CyberChef/releases/tag/v10.7.0 [10.6.0]: https://github.com/gchq/CyberChef/releases/tag/v10.6.0 @@ -551,6 +590,14 @@ All major and minor version changes will be documented in this file. Details of [@sg5506844]: https://github.com/sg5506844 [@AliceGrey]: https://github.com/AliceGrey [@AshCorr]: https://github.com/AshCorr +[@simonw]: https://github.com/simonw +[@chriswhite199]: https://github.com/chriswhite199 +[@breakersall]: https://github.com/breakersall +[@evanreichard]: https://github.com/evanreichard +[@devcydo]: https://github.com/devcydo +[@zb3]: https://github.com/zb3 +[@jkataja]: https://github.com/jkataja +[@tomgond]: https://github.com/tomgond [8ad18b]: https://github.com/gchq/CyberChef/commit/8ad18bc7db6d9ff184ba3518686293a7685bf7b7 @@ -561,6 +608,7 @@ All major and minor version changes will be documented in this file. Details of [a895d1d]: https://github.com/gchq/CyberChef/commit/a895d1d82a2f92d440a0c5eca2bc7c898107b737 [31a7f83]: https://github.com/gchq/CyberChef/commit/31a7f83b82e78927f89689f323fcb9185144d6ff [760eff4]: https://github.com/gchq/CyberChef/commit/760eff49b5307aaa3104c5e5b437ffe62299acd1 +[65ffd8d]: https://github.com/gchq/CyberChef/commit/65ffd8d65d88eb369f6f61a5d1d0f807179bffb7 [#95]: https://github.com/gchq/CyberChef/pull/299 [#173]: https://github.com/gchq/CyberChef/pull/173 @@ -677,4 +725,22 @@ All major and minor version changes will be documented in this file. Details of [#1667]: https://github.com/gchq/CyberChef/issues/1667 [#1555]: https://github.com/gchq/CyberChef/issues/1555 [#1694]: https://github.com/gchq/CyberChef/issues/1694 -[#1699]: https://github.com/gchq/CyberChef/issues/1694 +[#1699]: https://github.com/gchq/CyberChef/issues/1699 +[#1757]: https://github.com/gchq/CyberChef/issues/1757 +[#1752]: https://github.com/gchq/CyberChef/issues/1752 +[#1753]: https://github.com/gchq/CyberChef/issues/1753 +[#1750]: https://github.com/gchq/CyberChef/issues/1750 +[#1591]: https://github.com/gchq/CyberChef/issues/1591 +[#654]: https://github.com/gchq/CyberChef/issues/654 +[#1762]: https://github.com/gchq/CyberChef/issues/1762 +[#1606]: https://github.com/gchq/CyberChef/issues/1606 +[#1197]: https://github.com/gchq/CyberChef/issues/1197 +[#933]: https://github.com/gchq/CyberChef/issues/933 +[#1361]: https://github.com/gchq/CyberChef/issues/1361 +[#1765]: https://github.com/gchq/CyberChef/issues/1765 +[#1767]: https://github.com/gchq/CyberChef/issues/1767 +[#1769]: https://github.com/gchq/CyberChef/issues/1769 +[#1759]: https://github.com/gchq/CyberChef/issues/1759 +[#1504]: https://github.com/gchq/CyberChef/issues/1504 +[#512]: https://github.com/gchq/CyberChef/issues/512 +[#1732]: https://github.com/gchq/CyberChef/issues/1732 diff --git a/Gruntfile.js b/Gruntfile.js index 32ba9007..b040d98d 100755 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -86,10 +86,12 @@ module.exports = function (grunt) { // Project configuration - const compileTime = grunt.template.today("UTC:dd/mm/yyyy HH:MM:ss") + " UTC", + const compileYear = grunt.template.today("UTC:yyyy"), + compileTime = grunt.template.today("UTC:dd/mm/yyyy HH:MM:ss") + " UTC", pkg = grunt.file.readJSON("package.json"), webpackConfig = require("./webpack.config.js"), BUILD_CONSTANTS = { + COMPILE_YEAR: JSON.stringify(compileYear), COMPILE_TIME: JSON.stringify(compileTime), COMPILE_MSG: JSON.stringify(grunt.option("compile-msg") || grunt.option("msg") || ""), PKG_VERSION: JSON.stringify(pkg.version), @@ -125,6 +127,7 @@ module.exports = function (grunt) { filename: "index.html", template: "./src/web/html/index.html", chunks: ["main"], + compileYear: compileYear, compileTime: compileTime, version: pkg.version, minify: { @@ -227,6 +230,7 @@ module.exports = function (grunt) { filename: "index.html", template: "./src/web/html/index.html", chunks: ["main"], + compileYear: compileYear, compileTime: compileTime, version: pkg.version, }) diff --git a/package-lock.json b/package-lock.json index 5e6671a5..20935e27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "10.8.2", + "version": "10.15.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "10.8.2", + "version": "10.15.1", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -14,6 +14,7 @@ "@babel/polyfill": "^7.12.1", "@blu3r4y/lzma": "^2.3.3", "@wavesenterprise/crypto-gost-js": "^2.1.0-RC1", + "@xmldom/xmldom": "^0.8.0", "argon2-browser": "^1.18.0", "arrive": "^2.4.1", "avsc": "^5.7.7", @@ -45,6 +46,7 @@ "flat": "^6.0.1", "geodesy": "1.1.3", "highlight.js": "^11.9.0", + "ieee754": "^1.1.13", "jimp": "^0.16.13", "jquery": "3.7.1", "js-crc": "^0.2.0", @@ -92,7 +94,6 @@ "unorm": "^1.6.0", "utf8": "^3.0.0", "vkbeautify": "^0.99.3", - "xmldom": "^0.6.0", "xpath": "0.0.34", "xregexp": "^5.1.1", "zlibjs": "^0.3.1" @@ -114,7 +115,7 @@ "babel-plugin-dynamic-import-node": "^2.3.3", "babel-plugin-transform-builtin-extend": "1.1.2", "base64-loader": "^1.0.0", - "chromedriver": "^121.0.0", + "chromedriver": "^122.0.0", "cli-progress": "^3.12.0", "colors": "^1.4.0", "copy-webpack-plugin": "^12.0.2", @@ -2830,6 +2831,12 @@ "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true + }, "node_modules/@types/body-parser": { "version": "1.19.2", "dev": true, @@ -3174,6 +3181,14 @@ "@xtuc/long": "4.2.2" } }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -3242,15 +3257,15 @@ } }, "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", "dev": true, "dependencies": { - "debug": "4" + "debug": "^4.3.4" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 14" } }, "node_modules/ajv": { @@ -3601,6 +3616,18 @@ "node": "*" } }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/async": { "version": "3.2.3", "dev": true, @@ -3976,6 +4003,15 @@ "node": ">= 0.8" } }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/batch": { "version": "0.6.1", "dev": true, @@ -4708,17 +4744,17 @@ } }, "node_modules/chromedriver": { - "version": "121.0.0", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-121.0.0.tgz", - "integrity": "sha512-ZIKEdZrQAfuzT/RRofjl8/EZR99ghbdBXNTOcgJMKGP6N/UL6lHUX4n6ONWBV18pDvDFfQJ0x58h5AdOaXIOMw==", + "version": "122.0.6", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-122.0.6.tgz", + "integrity": "sha512-Q0r+QlUtiJWMQ5HdYaFa0CtBmLFq3n5JWfmq9mOC00UMBvWxku09gUkvBt457QnYfTM/XHqY/HTFOxHvATnTmA==", "dev": true, "hasInstallScript": true, "dependencies": { "@testim/chrome-version": "^1.1.4", - "axios": "^1.6.5", + "axios": "^1.6.7", "compare-versions": "^6.1.0", "extract-zip": "^2.0.1", - "https-proxy-agent": "^5.0.1", + "proxy-agent": "^6.4.0", "proxy-from-env": "^1.1.0", "tcp-port-used": "^1.0.2" }, @@ -5817,6 +5853,15 @@ "node": ">=12" } }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, "node_modules/data-urls": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", @@ -5975,6 +6020,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/delaunator": { "version": "5.0.0", "license": "ISC", @@ -7408,6 +7467,29 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "dev": true }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs-extra/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/fs-monkey": { "version": "1.0.3", "dev": true, @@ -7511,6 +7593,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-uri": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", + "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", + "dev": true, + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4", + "fs-extra": "^11.2.0" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/getobject": { "version": "1.0.2", "dev": true, @@ -8603,9 +8700,9 @@ } }, "node_modules/http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "dependencies": { "agent-base": "^7.1.0", @@ -8615,18 +8712,6 @@ "node": ">= 14" } }, - "node_modules/http-proxy-agent/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", - "dev": true, - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/http-proxy-middleware": { "version": "2.0.4", "dev": true, @@ -8667,16 +8752,16 @@ "license": "MIT" }, "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "dev": true, "dependencies": { - "agent-base": "6", + "agent-base": "^7.0.2", "debug": "4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/human-signals": { @@ -8875,6 +8960,19 @@ "loose-envify": "^1.0.0" } }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/ip-regex": { "version": "4.3.0", "dev": true, @@ -9502,6 +9600,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true + }, "node_modules/jsdom": { "version": "23.2.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-23.2.0.tgz", @@ -9542,31 +9646,6 @@ } } }, - "node_modules/jsdom/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", - "dev": true, - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/jsdom/node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", - "dev": true, - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/jsesc": { "version": "3.0.2", "license": "MIT", @@ -9603,6 +9682,27 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonfile/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/jsonpath-plus": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-8.0.0.tgz", @@ -10696,6 +10796,15 @@ "dev": true, "license": "MIT" }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/ngeohash": { "version": "0.6.3", "license": "MIT", @@ -11488,6 +11597,38 @@ "node": ">= 4" } }, + "node_modules/pac-proxy-agent": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz", + "integrity": "sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A==", + "dev": true, + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "pac-resolver": "^7.0.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/pad-stream": { "version": "2.0.0", "dev": true, @@ -12259,6 +12400,34 @@ "node": ">= 0.10" } }, + "node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -13192,6 +13361,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, "node_modules/snackbarjs": { "version": "1.1.0", "license": "ISC" @@ -13217,6 +13396,34 @@ "node": ">=0.8.0" } }, + "node_modules/socks": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.1.tgz", + "integrity": "sha512-B6w7tkwNid7ToxjZ08rQMT8M9BJAf8DKx8Ft4NivzH0zBUfd6jldGcisJn/RLgxcX3FPNDdNQCUEMMT79b+oCQ==", + "dev": true, + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz", + "integrity": "sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/sortablejs": { "version": "1.15.2", "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.2.tgz", @@ -13288,9 +13495,10 @@ } }, "node_modules/sprintf-js": { - "version": "1.1.2", - "dev": true, - "license": "BSD-3-Clause" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true }, "node_modules/ssdeep.js": { "version": "0.0.3" @@ -14835,13 +15043,6 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, - "node_modules/xmldom": { - "version": "0.6.0", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/xpath": { "version": "0.0.34", "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.34.tgz", diff --git a/package.json b/package.json index 334d88b5..c623c647 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "10.8.2", + "version": "10.15.1", "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": "^121.0.0", + "chromedriver": "^122.0.0", "cli-progress": "^3.12.0", "colors": "^1.4.0", "copy-webpack-plugin": "^12.0.2", @@ -122,6 +122,7 @@ "escodegen": "^2.1.0", "esprima": "^4.0.1", "exif-parser": "^0.1.12", + "ieee754": "^1.1.13", "fernet": "^0.3.2", "file-saver": "^2.0.5", "flat": "^6.0.1", @@ -174,7 +175,7 @@ "unorm": "^1.6.0", "utf8": "^3.0.0", "vkbeautify": "^0.99.3", - "xmldom": "^0.6.0", + "@xmldom/xmldom": "^0.8.0", "xpath": "0.0.34", "xregexp": "^5.1.1", "zlibjs": "^0.3.1" diff --git a/src/core/Utils.mjs b/src/core/Utils.mjs index 18b0e688..a9c381d7 100755 --- a/src/core/Utils.mjs +++ b/src/core/Utils.mjs @@ -893,7 +893,7 @@ class Utils { /** - * Converts a string to it's title case equivalent. + * Converts a string to its title case equivalent. * * @param {string} str * @returns string diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 8e300c76..8f8a14f9 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -14,6 +14,8 @@ "From Charcode", "To Decimal", "From Decimal", + "To Float", + "From Float", "To Binary", "From Binary", "To Octal", @@ -95,6 +97,8 @@ "RC4", "RC4 Drop", "ChaCha", + "Salsa20", + "XSalsa20", "Rabbit", "SM4 Encrypt", "SM4 Decrypt", @@ -151,7 +155,8 @@ "Typex", "Lorenz", "Colossus", - "SIGABA" + "SIGABA", + "XXTEA" ] }, { @@ -174,7 +179,8 @@ "RSA Verify", "RSA Encrypt", "RSA Decrypt", - "Parse SSH Host Key" + "Parse SSH Host Key", + "Parse CSR" ] }, { @@ -231,6 +237,7 @@ "VarInt Decode", "JA3 Fingerprint", "JA3S Fingerprint", + "JA4 Fingerprint", "HASSH Client Fingerprint", "HASSH Server Fingerprint", "Format MAC addresses", @@ -239,6 +246,7 @@ "Encode NetBIOS Name", "Decode NetBIOS Name", "Defang URL", + "Fang URL", "Defang IP Addresses" ] }, @@ -314,6 +322,7 @@ "To UNIX Timestamp", "Windows Filetime to UNIX Timestamp", "UNIX Timestamp to Windows Filetime", + "DateTime Delta", "Extract dates", "Get Time", "Sleep" @@ -330,6 +339,7 @@ "Extract domains", "Extract file paths", "Extract dates", + "Extract hashes", "Regular expression", "XPath expression", "JPath expression", @@ -378,7 +388,6 @@ "SHA2", "SHA3", "SM3", - "MurmurHash3", "Keccak", "Shake", "RIPEMD", @@ -403,6 +412,7 @@ "Scrypt", "NT Hash", "LM Hash", + "MurmurHash3", "Fletcher-8 Checksum", "Fletcher-16 Checksum", "Fletcher-32 Checksum", diff --git a/src/core/config/scripts/newOperation.mjs b/src/core/config/scripts/newOperation.mjs index fddeff97..1686f6eb 100644 --- a/src/core/config/scripts/newOperation.mjs +++ b/src/core/config/scripts/newOperation.mjs @@ -147,7 +147,7 @@ class ${moduleName} extends Operation { this.name = "${result.opName}"; this.module = "${result.module}"; this.description = "${(new EscapeString).run(result.description, ["Special chars", "Double"])}"; - this.infoURL = "${result.infoURL}"; + this.infoURL = "${result.infoURL}"; // Usually a Wikipedia link. Remember to remove localisation (i.e. https://wikipedia.org/etc rather than https://en.wikipedia.org/etc) this.inputType = "${result.inputType}"; this.outputType = "${result.outputType}"; this.args = [ diff --git a/src/core/lib/ChrEnc.mjs b/src/core/lib/ChrEnc.mjs index 6879d736..55fe3761 100644 --- a/src/core/lib/ChrEnc.mjs +++ b/src/core/lib/ChrEnc.mjs @@ -224,8 +224,85 @@ export function chrEncWidth(page) { * @copyright Crown Copyright 2019 * @license Apache-2.0 */ +export const UNICODE_NORMALISATION_FORMS = ["NFD", "NFC", "NFKD", "NFKC"]; + /** - * Character encoding format mappings. + * Detects whether the input buffer is valid UTF8. + * + * @param {ArrayBuffer} data + * @returns {number} - 0 = not UTF8, 1 = ASCII, 2 = UTF8 */ -export const UNICODE_NORMALISATION_FORMS = ["NFD", "NFC", "NFKD", "NFKC"]; +export function isUTF8(data) { + const bytes = new Uint8Array(data); + let i = 0; + let onlyASCII = true; + while (i < bytes.length) { + if (( // ASCII + bytes[i] === 0x09 || + bytes[i] === 0x0A || + bytes[i] === 0x0D || + (0x20 <= bytes[i] && bytes[i] <= 0x7E) + )) { + i += 1; + continue; + } + + onlyASCII = false; + + if (( // non-overlong 2-byte + (0xC2 <= bytes[i] && bytes[i] <= 0xDF) && + (0x80 <= bytes[i+1] && bytes[i+1] <= 0xBF) + )) { + i += 2; + continue; + } + + if (( // excluding overlongs + bytes[i] === 0xE0 && + (0xA0 <= bytes[i + 1] && bytes[i + 1] <= 0xBF) && + (0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF) + ) || + ( // straight 3-byte + ((0xE1 <= bytes[i] && bytes[i] <= 0xEC) || + bytes[i] === 0xEE || + bytes[i] === 0xEF) && + (0x80 <= bytes[i + 1] && bytes[i+1] <= 0xBF) && + (0x80 <= bytes[i+2] && bytes[i+2] <= 0xBF) + ) || + ( // excluding surrogates + bytes[i] === 0xED && + (0x80 <= bytes[i+1] && bytes[i+1] <= 0x9F) && + (0x80 <= bytes[i+2] && bytes[i+2] <= 0xBF) + )) { + i += 3; + continue; + } + + if (( // planes 1-3 + bytes[i] === 0xF0 && + (0x90 <= bytes[i + 1] && bytes[i + 1] <= 0xBF) && + (0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF) && + (0x80 <= bytes[i + 3] && bytes[i + 3] <= 0xBF) + ) || + ( // planes 4-15 + (0xF1 <= bytes[i] && bytes[i] <= 0xF3) && + (0x80 <= bytes[i + 1] && bytes[i + 1] <= 0xBF) && + (0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF) && + (0x80 <= bytes[i + 3] && bytes[i + 3] <= 0xBF) + ) || + ( // plane 16 + bytes[i] === 0xF4 && + (0x80 <= bytes[i + 1] && bytes[i + 1] <= 0x8F) && + (0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF) && + (0x80 <= bytes[i + 3] && bytes[i + 3] <= 0xBF) + )) { + i += 4; + continue; + } + + return 0; + } + + return onlyASCII ? 1 : 2; +} diff --git a/src/core/lib/CipherSaber2.mjs b/src/core/lib/CipherSaber2.mjs index bf3954e9..8189d961 100644 --- a/src/core/lib/CipherSaber2.mjs +++ b/src/core/lib/CipherSaber2.mjs @@ -4,7 +4,7 @@ * @license Apache-2.0 */ export function encode(tempIVP, key, rounds, input) { - const ivp = new Uint8Array(key.concat(tempIVP)); + const ivp = new Uint8Array([...key, ...tempIVP]); const state = new Array(256).fill(0); let j = 0, i = 0; const result = []; diff --git a/src/core/lib/FileSignatures.mjs b/src/core/lib/FileSignatures.mjs index 4cba4bc7..22132823 100644 --- a/src/core/lib/FileSignatures.mjs +++ b/src/core/lib/FileSignatures.mjs @@ -72,6 +72,27 @@ export const FILE_SIGNATURES = { }, extractor: extractWEBP }, + { + name: "High Efficiency Image File Format", + extension: "heic,heif", + mime: "image/heif", + description: "", + signature: { + 0: 0x00, + 1: 0x00, + 2: 0x00, + 3: [0x24, 0x18], + 4: 0x66, // ftypheic + 5: 0x74, + 6: 0x79, + 7: 0x70, + 8: 0x68, + 9: 0x65, + 10: 0x69, + 11: 0x63 + }, + extractor: null + }, { name: "Camera Image File Format", extension: "crw", @@ -2727,7 +2748,7 @@ export function extractGIF(bytes, offset) { stream.moveForwardsBy(11); // Loop until next Graphic Control Extension. - while (stream.getBytes(2) !== [0x21, 0xf9]) { + while (!Array.from(stream.getBytes(2)).equals([0x21, 0xf9])) { stream.moveBackwardsBy(2); stream.moveForwardsBy(stream.readInt(1)); if (!stream.readInt(1)) diff --git a/src/core/lib/JA4.mjs b/src/core/lib/JA4.mjs new file mode 100644 index 00000000..b0b423a2 --- /dev/null +++ b/src/core/lib/JA4.mjs @@ -0,0 +1,166 @@ +/** + * JA4 resources. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + * + * JA4 Copyright 2023 FoxIO, LLC. + * @license BSD-3-Clause + */ + +import OperationError from "../errors/OperationError.mjs"; +import { parseTLSRecord, parseHighestSupportedVersion, parseFirstALPNValue } from "./TLS.mjs"; +import { toHexFast } from "./Hex.mjs"; +import { runHash } from "./Hash.mjs"; +import Utils from "../Utils.mjs"; + + +/** + * Calculate the JA4 from a given TLS Client Hello Stream + * @param {Uint8Array} bytes + * @returns {string} + */ +export function toJA4(bytes) { + let tlsr = {}; + try { + tlsr = parseTLSRecord(bytes); + } catch (err) { + throw new OperationError("Data is not a valid TLS Client Hello. QUIC is not yet supported.\n" + err); + } + + /* QUIC + “q” or “t”, which denotes whether the hello packet is for QUIC or TCP. + TODO: Implement QUIC + */ + const ptype = "t"; + + /* TLS Version + TLS version is shown in 3 different places. If extension 0x002b exists (supported_versions), then the version + is the highest value in the extension. Remember to ignore GREASE values. If the extension doesn’t exist, then + the TLS version is the value of the Protocol Version. Handshake version (located at the top of the packet) + should be ignored. + */ + let version = tlsr.version.value; + for (const ext of tlsr.handshake.value.extensions.value) { + if (ext.type.value === "supported_versions") { + version = parseHighestSupportedVersion(ext.value.data); + break; + } + } + switch (version) { + case 0x0304: version = "13"; break; // TLS 1.3 + case 0x0303: version = "12"; break; // TLS 1.2 + case 0x0302: version = "11"; break; // TLS 1.1 + case 0x0301: version = "10"; break; // TLS 1.0 + case 0x0300: version = "s3"; break; // SSL 3.0 + case 0x0200: version = "s2"; break; // SSL 2.0 + case 0x0100: version = "s1"; break; // SSL 1.0 + default: version = "00"; // Unknown + } + + /* SNI + If the SNI extension (0x0000) exists, then the destination of the connection is a domain, or “d” in the fingerprint. + If the SNI does not exist, then the destination is an IP address, or “i”. + */ + let sni = "i"; + for (const ext of tlsr.handshake.value.extensions.value) { + if (ext.type.value === "server_name") { + sni = "d"; + break; + } + } + + /* Number of Ciphers + 2 character number of cipher suites, so if there’s 6 cipher suites in the hello packet, then the value should be “06”. + If there’s > 99, which there should never be, then output “99”. Remember, ignore GREASE values. They don’t count. + */ + let cipherLen = 0; + for (const cs of tlsr.handshake.value.cipherSuites.value) { + if (cs.value !== "GREASE") cipherLen++; + } + cipherLen = cipherLen > 99 ? "99" : cipherLen.toString().padStart(2, "0"); + + /* Number of Extensions + Same as counting ciphers. Ignore GREASE. Include SNI and ALPN. + */ + let extLen = 0; + for (const ext of tlsr.handshake.value.extensions.value) { + if (ext.type.value !== "GREASE") extLen++; + } + extLen = extLen > 99 ? "99" : extLen.toString().padStart(2, "0"); + + /* ALPN Extension Value + The first and last characters of the ALPN (Application-Layer Protocol Negotiation) first value. + If there are no ALPN values or no ALPN extension then we print “00” as the value in the fingerprint. + */ + let alpn = "00"; + for (const ext of tlsr.handshake.value.extensions.value) { + if (ext.type.value === "application_layer_protocol_negotiation") { + alpn = parseFirstALPNValue(ext.value.data); + alpn = alpn.charAt(0) + alpn.charAt(alpn.length - 1); + break; + } + } + + /* Cipher hash + A 12 character truncated sha256 hash of the list of ciphers sorted in hex order, first 12 characters. + The list is created using the 4 character hex values of the ciphers, lower case, comma delimited, ignoring GREASE. + */ + const originalCiphersList = []; + for (const cs of tlsr.handshake.value.cipherSuites.value) { + if (cs.value !== "GREASE") { + originalCiphersList.push(toHexFast(cs.data)); + } + } + const sortedCiphersList = [...originalCiphersList].sort(); + const sortedCiphersRaw = sortedCiphersList.join(","); + const originalCiphersRaw = originalCiphersList.join(","); + const sortedCiphers = runHash( + "sha256", + Utils.strToArrayBuffer(sortedCiphersRaw) + ).substring(0, 12); + const originalCiphers = runHash( + "sha256", + Utils.strToArrayBuffer(originalCiphersRaw) + ).substring(0, 12); + + /* Extension hash + A 12 character truncated sha256 hash of the list of extensions, sorted by hex value, followed by the list of signature + algorithms, in the order that they appear (not sorted). + The extension list is created using the 4 character hex values of the extensions, lower case, comma delimited, sorted + (not in the order they appear). Ignore the SNI extension (0000) and the ALPN extension (0010) as we’ve already captured + them in the a section of the fingerprint. These values are omitted so that the same application would have the same b + section of the fingerprint regardless of if it were going to a domain, IP, or changing ALPNs. + */ + const originalExtensionsList = []; + let signatureAlgorithms = ""; + for (const ext of tlsr.handshake.value.extensions.value) { + if (ext.type.value !== "GREASE") { + originalExtensionsList.push(toHexFast(ext.type.data)); + } + if (ext.type.value === "signature_algorithms") { + signatureAlgorithms = toHexFast(ext.value.data.slice(2)); + signatureAlgorithms = signatureAlgorithms.replace(/(.{4})/g, "$1,"); + signatureAlgorithms = signatureAlgorithms.substring(0, signatureAlgorithms.length - 1); + } + } + const sortedExtensionsList = [...originalExtensionsList].filter(e => e !== "0000" && e !== "0010").sort(); + const sortedExtensionsRaw = sortedExtensionsList.join(",") + "_" + signatureAlgorithms; + const originalExtensionsRaw = originalExtensionsList.join(",") + "_" + signatureAlgorithms; + const sortedExtensions = runHash( + "sha256", + Utils.strToArrayBuffer(sortedExtensionsRaw) + ).substring(0, 12); + const originalExtensions = runHash( + "sha256", + Utils.strToArrayBuffer(originalExtensionsRaw) + ).substring(0, 12); + + return { + "JA4": `${ptype}${version}${sni}${cipherLen}${extLen}${alpn}_${sortedCiphers}_${sortedExtensions}`, + "JA4_o": `${ptype}${version}${sni}${cipherLen}${extLen}${alpn}_${originalCiphers}_${originalExtensions}`, + "JA4_r": `${ptype}${version}${sni}${cipherLen}${extLen}${alpn}_${sortedCiphersRaw}_${sortedExtensionsRaw}`, + "JA4_ro": `${ptype}${version}${sni}${cipherLen}${extLen}${alpn}_${originalCiphersRaw}_${originalExtensionsRaw}`, + }; +} diff --git a/src/core/lib/Magic.mjs b/src/core/lib/Magic.mjs index 921fc3f6..14111ec7 100644 --- a/src/core/lib/Magic.mjs +++ b/src/core/lib/Magic.mjs @@ -3,6 +3,7 @@ import Utils, { isWorkerEnvironment } from "../Utils.mjs"; import Recipe from "../Recipe.mjs"; import Dish from "../Dish.mjs"; import {detectFileType, isType} from "./FileType.mjs"; +import {isUTF8} from "./ChrEnc.mjs"; import chiSquared from "chi-squared"; /** @@ -111,82 +112,6 @@ class Magic { }; } - /** - * Detects whether the input buffer is valid UTF8. - * - * @returns {boolean} - */ - isUTF8() { - const bytes = new Uint8Array(this.inputBuffer); - let i = 0; - while (i < bytes.length) { - if (( // ASCII - bytes[i] === 0x09 || - bytes[i] === 0x0A || - bytes[i] === 0x0D || - (0x20 <= bytes[i] && bytes[i] <= 0x7E) - )) { - i += 1; - continue; - } - - if (( // non-overlong 2-byte - (0xC2 <= bytes[i] && bytes[i] <= 0xDF) && - (0x80 <= bytes[i+1] && bytes[i+1] <= 0xBF) - )) { - i += 2; - continue; - } - - if (( // excluding overlongs - bytes[i] === 0xE0 && - (0xA0 <= bytes[i + 1] && bytes[i + 1] <= 0xBF) && - (0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF) - ) || - ( // straight 3-byte - ((0xE1 <= bytes[i] && bytes[i] <= 0xEC) || - bytes[i] === 0xEE || - bytes[i] === 0xEF) && - (0x80 <= bytes[i + 1] && bytes[i+1] <= 0xBF) && - (0x80 <= bytes[i+2] && bytes[i+2] <= 0xBF) - ) || - ( // excluding surrogates - bytes[i] === 0xED && - (0x80 <= bytes[i+1] && bytes[i+1] <= 0x9F) && - (0x80 <= bytes[i+2] && bytes[i+2] <= 0xBF) - )) { - i += 3; - continue; - } - - if (( // planes 1-3 - bytes[i] === 0xF0 && - (0x90 <= bytes[i + 1] && bytes[i + 1] <= 0xBF) && - (0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF) && - (0x80 <= bytes[i + 3] && bytes[i + 3] <= 0xBF) - ) || - ( // planes 4-15 - (0xF1 <= bytes[i] && bytes[i] <= 0xF3) && - (0x80 <= bytes[i + 1] && bytes[i + 1] <= 0xBF) && - (0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF) && - (0x80 <= bytes[i + 3] && bytes[i + 3] <= 0xBF) - ) || - ( // plane 16 - bytes[i] === 0xF4 && - (0x80 <= bytes[i + 1] && bytes[i + 1] <= 0x8F) && - (0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF) && - (0x80 <= bytes[i + 3] && bytes[i + 3] <= 0xBF) - )) { - i += 4; - continue; - } - - return false; - } - - return true; - } - /** * Calculates the Shannon entropy of the input data. * @@ -336,7 +261,7 @@ class Magic { data: this.inputStr.slice(0, 100), languageScores: this.detectLanguage(extLang), fileType: this.detectFileType(), - isUTF8: this.isUTF8(), + isUTF8: !!isUTF8(this.inputBuffer), entropy: this.calcEntropy(), matchingOps: matchingOps, useful: useful, diff --git a/src/core/lib/Salsa20.mjs b/src/core/lib/Salsa20.mjs new file mode 100644 index 00000000..d72831bf --- /dev/null +++ b/src/core/lib/Salsa20.mjs @@ -0,0 +1,144 @@ +/** + * @author joostrijneveld [joost@joostrijneveld.nl] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Utils from "../Utils.mjs"; + +/** + * Computes the Salsa20 permute function + * + * @param {byteArray} x + * @param {integer} rounds + */ +function salsa20Permute(x, rounds) { + /** + * Macro to compute a 32-bit rotate-left operation + * + * @param {integer} x + * @param {integer} n + * @returns {integer} + */ + function ROL32(x, n) { + return ((x << n) & 0xFFFFFFFF) | (x >>> (32 - n)); + } + + /** + * Macro to compute a single Salsa20 quarterround operation + * + * @param {integer} x + * @param {integer} a + * @param {integer} b + * @param {integer} c + * @param {integer} d + * @returns {integer} + */ + function quarterround(x, a, b, c, d) { + x[b] ^= ROL32((x[a] + x[d]) & 0xFFFFFFFF, 7); + x[c] ^= ROL32((x[b] + x[a]) & 0xFFFFFFFF, 9); + x[d] ^= ROL32((x[c] + x[b]) & 0xFFFFFFFF, 13); + x[a] ^= ROL32((x[d] + x[c]) & 0xFFFFFFFF, 18); + } + + for (let i = 0; i < rounds / 2; i++) { + quarterround(x, 0, 4, 8, 12); + quarterround(x, 5, 9, 13, 1); + quarterround(x, 10, 14, 2, 6); + quarterround(x, 15, 3, 7, 11); + quarterround(x, 0, 1, 2, 3); + quarterround(x, 5, 6, 7, 4); + quarterround(x, 10, 11, 8, 9); + quarterround(x, 15, 12, 13, 14); + } +} + +/** + * Computes the Salsa20 block function + * + * @param {byteArray} key + * @param {byteArray} nonce + * @param {byteArray} counter + * @param {integer} rounds + * @returns {byteArray} + */ +export function salsa20Block(key, nonce, counter, rounds) { + const tau = "expand 16-byte k"; + const sigma = "expand 32-byte k"; + let state, c; + if (key.length === 16) { + c = Utils.strToByteArray(tau); + key = key.concat(key); + } else { + c = Utils.strToByteArray(sigma); + } + + state = c.slice(0, 4); + state = state.concat(key.slice(0, 16)); + state = state.concat(c.slice(4, 8)); + state = state.concat(nonce); + state = state.concat(counter); + state = state.concat(c.slice(8, 12)); + state = state.concat(key.slice(16, 32)); + state = state.concat(c.slice(12, 16)); + + const x = Array(); + for (let i = 0; i < 64; i += 4) { + x.push(Utils.byteArrayToInt(state.slice(i, i + 4), "little")); + } + const a = [...x]; + + salsa20Permute(x, rounds); + + for (let i = 0; i < 16; i++) { + x[i] = (x[i] + a[i]) & 0xFFFFFFFF; + } + + let output = Array(); + for (let i = 0; i < 16; i++) { + output = output.concat(Utils.intToByteArray(x[i], 4, "little")); + } + return output; +} + +/** + * Computes the hSalsa20 function + * + * @param {byteArray} key + * @param {byteArray} nonce + * @param {integer} rounds + * @returns {byteArray} + */ +export function hsalsa20(key, nonce, rounds) { + const tau = "expand 16-byte k"; + const sigma = "expand 32-byte k"; + let state, c; + if (key.length === 16) { + c = Utils.strToByteArray(tau); + key = key.concat(key); + } else { + c = Utils.strToByteArray(sigma); + } + + state = c.slice(0, 4); + state = state.concat(key.slice(0, 16)); + state = state.concat(c.slice(4, 8)); + state = state.concat(nonce); + state = state.concat(c.slice(8, 12)); + state = state.concat(key.slice(16, 32)); + state = state.concat(c.slice(12, 16)); + + const x = Array(); + for (let i = 0; i < 64; i += 4) { + x.push(Utils.byteArrayToInt(state.slice(i, i + 4), "little")); + } + + salsa20Permute(x, rounds); + + let output = Array(); + const idx = [0, 5, 10, 15, 6, 7, 8, 9]; + for (let i = 0; i < 8; i++) { + output = output.concat(Utils.intToByteArray(x[idx[i]], 4, "little")); + } + return output; +} diff --git a/src/core/lib/Stream.mjs b/src/core/lib/Stream.mjs index 18ce71c3..8253c4cf 100644 --- a/src/core/lib/Stream.mjs +++ b/src/core/lib/Stream.mjs @@ -18,12 +18,23 @@ export default class Stream { * Stream constructor. * * @param {Uint8Array} input + * @param {number} pos + * @param {number} bitPos */ - constructor(input) { + constructor(input, pos=0, bitPos=0) { this.bytes = input; this.length = this.bytes.length; - this.position = 0; - this.bitPos = 0; + this.position = pos; + this.bitPos = bitPos; + } + + /** + * Clone this Stream returning a new identical Stream. + * + * @returns {Stream} + */ + clone() { + return new Stream(this.bytes, this.position, this.bitPos); } /** diff --git a/src/core/lib/TLS.mjs b/src/core/lib/TLS.mjs new file mode 100644 index 00000000..e3f18eb3 --- /dev/null +++ b/src/core/lib/TLS.mjs @@ -0,0 +1,776 @@ +/** + * TLS resources. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import OperationError from "../errors/OperationError.mjs"; +import Stream from "../lib/Stream.mjs"; + +/** + * Parse a TLS Record + * @param {Uint8Array} bytes + * @returns {JSON} + */ +export function parseTLSRecord(bytes) { + const s = new Stream(bytes); + const b = s.clone(); + const r = {}; + + // Content type + r.contentType = { + description: "Content Type", + length: 1, + data: b.getBytes(1), + value: s.readInt(1) + }; + if (r.contentType.value !== 0x16) + throw new OperationError("Not handshake data."); + + // Version + r.version = { + description: "Protocol Version", + length: 2, + data: b.getBytes(2), + value: s.readInt(2) + }; + + // Length + r.length = { + description: "Record Length", + length: 2, + data: b.getBytes(2), + value: s.readInt(2) + }; + if (s.length !== r.length.value + 5) + throw new OperationError("Incorrect handshake length."); + + // Handshake + r.handshake = { + description: "Handshake", + length: r.length.value, + data: b.getBytes(r.length.value), + value: parseHandshake(s.getBytes(r.length.value)) + }; + + return r; +} + +/** + * Parse a TLS Handshake + * @param {Uint8Array} bytes + * @returns {JSON} + */ +function parseHandshake(bytes) { + const s = new Stream(bytes); + const b = s.clone(); + const h = {}; + + // Handshake type + h.handshakeType = { + description: "Client Hello", + length: 1, + data: b.getBytes(1), + value: s.readInt(1) + }; + if (h.handshakeType.value !== 0x01) + throw new OperationError("Not a Client Hello."); + + // Handshake length + h.handshakeLength = { + description: "Handshake Length", + length: 3, + data: b.getBytes(3), + value: s.readInt(3) + }; + if (s.length !== h.handshakeLength.value + 4) + throw new OperationError("Not enough data in Client Hello."); + + // Hello version + h.helloVersion = { + description: "Client Hello Version", + length: 2, + data: b.getBytes(2), + value: s.readInt(2) + }; + + // Random + h.random = { + description: "Client Random", + length: 32, + data: b.getBytes(32), + value: s.getBytes(32) + }; + + // Session ID Length + h.sessionIDLength = { + description: "Session ID Length", + length: 1, + data: b.getBytes(1), + value: s.readInt(1) + }; + + // Session ID + h.sessionID = { + description: "Session ID", + length: h.sessionIDLength.value, + data: b.getBytes(h.sessionIDLength.value), + value: s.getBytes(h.sessionIDLength.value) + }; + + // Cipher Suites Length + h.cipherSuitesLength = { + description: "Cipher Suites Length", + length: 2, + data: b.getBytes(2), + value: s.readInt(2) + }; + + // Cipher Suites + h.cipherSuites = { + description: "Cipher Suites", + length: h.cipherSuitesLength.value, + data: b.getBytes(h.cipherSuitesLength.value), + value: parseCipherSuites(s.getBytes(h.cipherSuitesLength.value)) + }; + + // Compression Methods Length + h.compressionMethodsLength = { + description: "Compression Methods Length", + length: 1, + data: b.getBytes(1), + value: s.readInt(1) + }; + + // Compression Methods + h.compressionMethods = { + description: "Compression Methods", + length: h.compressionMethodsLength.value, + data: b.getBytes(h.compressionMethodsLength.value), + value: parseCompressionMethods(s.getBytes(h.compressionMethodsLength.value)) + }; + + // Extensions Length + h.extensionsLength = { + description: "Extensions Length", + length: 2, + data: b.getBytes(2), + value: s.readInt(2) + }; + + // Extensions + h.extensions = { + description: "Extensions", + length: h.extensionsLength.value, + data: b.getBytes(h.extensionsLength.value), + value: parseExtensions(s.getBytes(h.extensionsLength.value)) + }; + + return h; +} + +/** + * Parse Cipher Suites + * @param {Uint8Array} bytes + * @returns {JSON} + */ +function parseCipherSuites(bytes) { + const s = new Stream(bytes); + const b = s.clone(); + const cs = []; + + while (s.hasMore()) { + cs.push({ + description: "Cipher Suite", + length: 2, + data: b.getBytes(2), + value: CIPHER_SUITES_LOOKUP[s.readInt(2)] || "Unknown" + }); + } + return cs; +} + +/** + * Parse Compression Methods + * @param {Uint8Array} bytes + * @returns {JSON} + */ +function parseCompressionMethods(bytes) { + const s = new Stream(bytes); + const b = s.clone(); + const cm = []; + + while (s.hasMore()) { + cm.push({ + description: "Compression Method", + length: 1, + data: b.getBytes(1), + value: s.readInt(1) // TODO: Compression method name here + }); + } + return cm; +} + +/** + * Parse Extensions + * @param {Uint8Array} bytes + * @returns {JSON} + */ +function parseExtensions(bytes) { + const s = new Stream(bytes); + const b = s.clone(); + + const exts = []; + while (s.hasMore()) { + const ext = {}; + + // Type + ext.type = { + description: "Extension Type", + length: 2, + data: b.getBytes(2), + value: EXTENSION_LOOKUP[s.readInt(2)] || "unknown" + }; + + // Length + ext.length = { + description: "Extension Length", + length: 2, + data: b.getBytes(2), + value: s.readInt(2) + }; + + // Value + ext.value = { + description: "Extension Value", + length: ext.length.value, + data: b.getBytes(ext.length.value), + value: s.getBytes(ext.length.value) + }; + + exts.push(ext); + } + + return exts; +} + +/** + * Extension type lookup table + */ +const EXTENSION_LOOKUP = { + 0: "server_name", + 1: "max_fragment_length", + 2: "client_certificate_url", + 3: "trusted_ca_keys", + 4: "truncated_hmac", + 5: "status_request", + 6: "user_mapping", + 7: "client_authz", + 8: "server_authz", + 9: "cert_type", + 10: "supported_groups", + 11: "ec_point_formats", + 12: "srp", + 13: "signature_algorithms", + 14: "use_srtp", + 15: "heartbeat", + 16: "application_layer_protocol_negotiation", + 17: "status_request_v2", + 18: "signed_certificate_timestamp", + 19: "client_certificate_type", + 20: "server_certificate_type", + 21: "padding", + 22: "encrypt_then_mac", + 23: "extended_master_secret", + 24: "token_binding", + 25: "cached_info", + 26: "tls_lts", + 27: "compress_certificate", + 28: "record_size_limit", + 29: "pwd_protect", + 30: "pwd_clear", + 31: "password_salt", + 32: "ticket_pinning", + 33: "tls_cert_with_extern_psk", + 34: "delegated_credential", + 35: "session_ticket", + 36: "TLMSP", + 37: "TLMSP_proxying", + 38: "TLMSP_delegate", + 39: "supported_ekt_ciphers", + 40: "Reserved", + 41: "pre_shared_key", + 42: "early_data", + 43: "supported_versions", + 44: "cookie", + 45: "psk_key_exchange_modes", + 46: "Reserved", + 47: "certificate_authorities", + 48: "oid_filters", + 49: "post_handshake_auth", + 50: "signature_algorithms_cert", + 51: "key_share", + 52: "transparency_info", + 53: "connection_id (deprecated)", + 54: "connection_id", + 55: "external_id_hash", + 56: "external_session_id", + 57: "quic_transport_parameters", + 58: "ticket_request", + 59: "dnssec_chain", + 60: "sequence_number_encryption_algorithms", + 61: "rrc", + 2570: "GREASE", + 6682: "GREASE", + 10794: "GREASE", + 14906: "GREASE", + 17513: "application_settings", + 19018: "GREASE", + 23130: "GREASE", + 27242: "GREASE", + 31354: "GREASE", + 35466: "GREASE", + 39578: "GREASE", + 43690: "GREASE", + 47802: "GREASE", + 51914: "GREASE", + 56026: "GREASE", + 60138: "GREASE", + 64250: "GREASE", + 64768: "ech_outer_extensions", + 65037: "encrypted_client_hello", + 65281: "renegotiation_info" +}; + +/** + * Cipher suites lookup table + */ +const CIPHER_SUITES_LOOKUP = { + 0x0000: "TLS_NULL_WITH_NULL_NULL", + 0x0001: "TLS_RSA_WITH_NULL_MD5", + 0x0002: "TLS_RSA_WITH_NULL_SHA", + 0x0003: "TLS_RSA_EXPORT_WITH_RC4_40_MD5", + 0x0004: "TLS_RSA_WITH_RC4_128_MD5", + 0x0005: "TLS_RSA_WITH_RC4_128_SHA", + 0x0006: "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", + 0x0007: "TLS_RSA_WITH_IDEA_CBC_SHA", + 0x0008: "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", + 0x0009: "TLS_RSA_WITH_DES_CBC_SHA", + 0x000A: "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + 0x000B: "TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA", + 0x000C: "TLS_DH_DSS_WITH_DES_CBC_SHA", + 0x000D: "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", + 0x000E: "TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA", + 0x000F: "TLS_DH_RSA_WITH_DES_CBC_SHA", + 0x0010: "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", + 0x0011: "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", + 0x0012: "TLS_DHE_DSS_WITH_DES_CBC_SHA", + 0x0013: "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", + 0x0014: "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", + 0x0015: "TLS_DHE_RSA_WITH_DES_CBC_SHA", + 0x0016: "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", + 0x0017: "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", + 0x0018: "TLS_DH_anon_WITH_RC4_128_MD5", + 0x0019: "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", + 0x001A: "TLS_DH_anon_WITH_DES_CBC_SHA", + 0x001B: "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", + 0x001E: "TLS_KRB5_WITH_DES_CBC_SHA", + 0x001F: "TLS_KRB5_WITH_3DES_EDE_CBC_SHA", + 0x0020: "TLS_KRB5_WITH_RC4_128_SHA", + 0x0021: "TLS_KRB5_WITH_IDEA_CBC_SHA", + 0x0022: "TLS_KRB5_WITH_DES_CBC_MD5", + 0x0023: "TLS_KRB5_WITH_3DES_EDE_CBC_MD5", + 0x0024: "TLS_KRB5_WITH_RC4_128_MD5", + 0x0025: "TLS_KRB5_WITH_IDEA_CBC_MD5", + 0x0026: "TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA", + 0x0027: "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA", + 0x0028: "TLS_KRB5_EXPORT_WITH_RC4_40_SHA", + 0x0029: "TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5", + 0x002A: "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5", + 0x002B: "TLS_KRB5_EXPORT_WITH_RC4_40_MD5", + 0x002C: "TLS_PSK_WITH_NULL_SHA", + 0x002D: "TLS_DHE_PSK_WITH_NULL_SHA", + 0x002E: "TLS_RSA_PSK_WITH_NULL_SHA", + 0x002F: "TLS_RSA_WITH_AES_128_CBC_SHA", + 0x0030: "TLS_DH_DSS_WITH_AES_128_CBC_SHA", + 0x0031: "TLS_DH_RSA_WITH_AES_128_CBC_SHA", + 0x0032: "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + 0x0033: "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + 0x0034: "TLS_DH_anon_WITH_AES_128_CBC_SHA", + 0x0035: "TLS_RSA_WITH_AES_256_CBC_SHA", + 0x0036: "TLS_DH_DSS_WITH_AES_256_CBC_SHA", + 0x0037: "TLS_DH_RSA_WITH_AES_256_CBC_SHA", + 0x0038: "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", + 0x0039: "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + 0x003A: "TLS_DH_anon_WITH_AES_256_CBC_SHA", + 0x003B: "TLS_RSA_WITH_NULL_SHA256", + 0x003C: "TLS_RSA_WITH_AES_128_CBC_SHA256", + 0x003D: "TLS_RSA_WITH_AES_256_CBC_SHA256", + 0x003E: "TLS_DH_DSS_WITH_AES_128_CBC_SHA256", + 0x003F: "TLS_DH_RSA_WITH_AES_128_CBC_SHA256", + 0x0040: "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", + 0x0041: "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", + 0x0042: "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", + 0x0043: "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", + 0x0044: "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", + 0x0045: "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", + 0x0046: "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", + 0x0067: "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", + 0x0068: "TLS_DH_DSS_WITH_AES_256_CBC_SHA256", + 0x0069: "TLS_DH_RSA_WITH_AES_256_CBC_SHA256", + 0x006A: "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", + 0x006B: "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", + 0x006C: "TLS_DH_anon_WITH_AES_128_CBC_SHA256", + 0x006D: "TLS_DH_anon_WITH_AES_256_CBC_SHA256", + 0x0084: "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", + 0x0085: "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", + 0x0086: "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", + 0x0087: "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", + 0x0088: "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", + 0x0089: "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", + 0x008A: "TLS_PSK_WITH_RC4_128_SHA", + 0x008B: "TLS_PSK_WITH_3DES_EDE_CBC_SHA", + 0x008C: "TLS_PSK_WITH_AES_128_CBC_SHA", + 0x008D: "TLS_PSK_WITH_AES_256_CBC_SHA", + 0x008E: "TLS_DHE_PSK_WITH_RC4_128_SHA", + 0x008F: "TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA", + 0x0090: "TLS_DHE_PSK_WITH_AES_128_CBC_SHA", + 0x0091: "TLS_DHE_PSK_WITH_AES_256_CBC_SHA", + 0x0092: "TLS_RSA_PSK_WITH_RC4_128_SHA", + 0x0093: "TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA", + 0x0094: "TLS_RSA_PSK_WITH_AES_128_CBC_SHA", + 0x0095: "TLS_RSA_PSK_WITH_AES_256_CBC_SHA", + 0x0096: "TLS_RSA_WITH_SEED_CBC_SHA", + 0x0097: "TLS_DH_DSS_WITH_SEED_CBC_SHA", + 0x0098: "TLS_DH_RSA_WITH_SEED_CBC_SHA", + 0x0099: "TLS_DHE_DSS_WITH_SEED_CBC_SHA", + 0x009A: "TLS_DHE_RSA_WITH_SEED_CBC_SHA", + 0x009B: "TLS_DH_anon_WITH_SEED_CBC_SHA", + 0x009C: "TLS_RSA_WITH_AES_128_GCM_SHA256", + 0x009D: "TLS_RSA_WITH_AES_256_GCM_SHA384", + 0x009E: "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", + 0x009F: "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + 0x00A0: "TLS_DH_RSA_WITH_AES_128_GCM_SHA256", + 0x00A1: "TLS_DH_RSA_WITH_AES_256_GCM_SHA384", + 0x00A2: "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", + 0x00A3: "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", + 0x00A4: "TLS_DH_DSS_WITH_AES_128_GCM_SHA256", + 0x00A5: "TLS_DH_DSS_WITH_AES_256_GCM_SHA384", + 0x00A6: "TLS_DH_anon_WITH_AES_128_GCM_SHA256", + 0x00A7: "TLS_DH_anon_WITH_AES_256_GCM_SHA384", + 0x00A8: "TLS_PSK_WITH_AES_128_GCM_SHA256", + 0x00A9: "TLS_PSK_WITH_AES_256_GCM_SHA384", + 0x00AA: "TLS_DHE_PSK_WITH_AES_128_GCM_SHA256", + 0x00AB: "TLS_DHE_PSK_WITH_AES_256_GCM_SHA384", + 0x00AC: "TLS_RSA_PSK_WITH_AES_128_GCM_SHA256", + 0x00AD: "TLS_RSA_PSK_WITH_AES_256_GCM_SHA384", + 0x00AE: "TLS_PSK_WITH_AES_128_CBC_SHA256", + 0x00AF: "TLS_PSK_WITH_AES_256_CBC_SHA384", + 0x00B0: "TLS_PSK_WITH_NULL_SHA256", + 0x00B1: "TLS_PSK_WITH_NULL_SHA384", + 0x00B2: "TLS_DHE_PSK_WITH_AES_128_CBC_SHA256", + 0x00B3: "TLS_DHE_PSK_WITH_AES_256_CBC_SHA384", + 0x00B4: "TLS_DHE_PSK_WITH_NULL_SHA256", + 0x00B5: "TLS_DHE_PSK_WITH_NULL_SHA384", + 0x00B6: "TLS_RSA_PSK_WITH_AES_128_CBC_SHA256", + 0x00B7: "TLS_RSA_PSK_WITH_AES_256_CBC_SHA384", + 0x00B8: "TLS_RSA_PSK_WITH_NULL_SHA256", + 0x00B9: "TLS_RSA_PSK_WITH_NULL_SHA384", + 0x00BA: "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", + 0x00BB: "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256", + 0x00BC: "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256", + 0x00BD: "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256", + 0x00BE: "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", + 0x00BF: "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256", + 0x00C0: "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", + 0x00C1: "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256", + 0x00C2: "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256", + 0x00C3: "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256", + 0x00C4: "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", + 0x00C5: "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256", + 0x00C6: "TLS_SM4_GCM_SM3", + 0x00C7: "TLS_SM4_CCM_SM3", + 0x00FF: "TLS_EMPTY_RENEGOTIATION_INFO_SCSV", + 0x0A0A: "GREASE", + 0x1301: "TLS_AES_128_GCM_SHA256", + 0x1302: "TLS_AES_256_GCM_SHA384", + 0x1303: "TLS_CHACHA20_POLY1305_SHA256", + 0x1304: "TLS_AES_128_CCM_SHA256", + 0x1305: "TLS_AES_128_CCM_8_SHA256", + 0x1306: "TLS_AEGIS_256_SHA512", + 0x1307: "TLS_AEGIS_128L_SHA256", + 0x1A1A: "GREASE", + 0x2A2A: "GREASE", + 0x3A3A: "GREASE", + 0x4A4A: "GREASE", + 0x5600: "TLS_FALLBACK_SCSV", + 0x5A5A: "GREASE", + 0x6A6A: "GREASE", + 0x7A7A: "GREASE", + 0x8A8A: "GREASE", + 0x9A9A: "GREASE", + 0xAAAA: "GREASE", + 0xBABA: "GREASE", + 0xC001: "TLS_ECDH_ECDSA_WITH_NULL_SHA", + 0xC002: "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", + 0xC003: "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", + 0xC004: "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", + 0xC005: "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", + 0xC006: "TLS_ECDHE_ECDSA_WITH_NULL_SHA", + 0xC007: "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", + 0xC008: "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", + 0xC009: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + 0xC00A: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + 0xC00B: "TLS_ECDH_RSA_WITH_NULL_SHA", + 0xC00C: "TLS_ECDH_RSA_WITH_RC4_128_SHA", + 0xC00D: "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", + 0xC00E: "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", + 0xC00F: "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", + 0xC010: "TLS_ECDHE_RSA_WITH_NULL_SHA", + 0xC011: "TLS_ECDHE_RSA_WITH_RC4_128_SHA", + 0xC012: "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", + 0xC013: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + 0xC014: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + 0xC015: "TLS_ECDH_anon_WITH_NULL_SHA", + 0xC016: "TLS_ECDH_anon_WITH_RC4_128_SHA", + 0xC017: "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", + 0xC018: "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", + 0xC019: "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", + 0xC01A: "TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA", + 0xC01B: "TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA", + 0xC01C: "TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA", + 0xC01D: "TLS_SRP_SHA_WITH_AES_128_CBC_SHA", + 0xC01E: "TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA", + 0xC01F: "TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA", + 0xC020: "TLS_SRP_SHA_WITH_AES_256_CBC_SHA", + 0xC021: "TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA", + 0xC022: "TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA", + 0xC023: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + 0xC024: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", + 0xC025: "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", + 0xC026: "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", + 0xC027: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + 0xC028: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", + 0xC029: "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", + 0xC02A: "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", + 0xC02B: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + 0xC02C: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + 0xC02D: "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", + 0xC02E: "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", + 0xC02F: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + 0xC030: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + 0xC031: "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", + 0xC032: "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", + 0xC033: "TLS_ECDHE_PSK_WITH_RC4_128_SHA", + 0xC034: "TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA", + 0xC035: "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA", + 0xC036: "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA", + 0xC037: "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256", + 0xC038: "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384", + 0xC039: "TLS_ECDHE_PSK_WITH_NULL_SHA", + 0xC03A: "TLS_ECDHE_PSK_WITH_NULL_SHA256", + 0xC03B: "TLS_ECDHE_PSK_WITH_NULL_SHA384", + 0xC03C: "TLS_RSA_WITH_ARIA_128_CBC_SHA256", + 0xC03D: "TLS_RSA_WITH_ARIA_256_CBC_SHA384", + 0xC03E: "TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256", + 0xC03F: "TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384", + 0xC040: "TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256", + 0xC041: "TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384", + 0xC042: "TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256", + 0xC043: "TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384", + 0xC044: "TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256", + 0xC045: "TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384", + 0xC046: "TLS_DH_anon_WITH_ARIA_128_CBC_SHA256", + 0xC047: "TLS_DH_anon_WITH_ARIA_256_CBC_SHA384", + 0xC048: "TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256", + 0xC049: "TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384", + 0xC04A: "TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256", + 0xC04B: "TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384", + 0xC04C: "TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256", + 0xC04D: "TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384", + 0xC04E: "TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256", + 0xC04F: "TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384", + 0xC050: "TLS_RSA_WITH_ARIA_128_GCM_SHA256", + 0xC051: "TLS_RSA_WITH_ARIA_256_GCM_SHA384", + 0xC052: "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256", + 0xC053: "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384", + 0xC054: "TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256", + 0xC055: "TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384", + 0xC056: "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256", + 0xC057: "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384", + 0xC058: "TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256", + 0xC059: "TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384", + 0xC05A: "TLS_DH_anon_WITH_ARIA_128_GCM_SHA256", + 0xC05B: "TLS_DH_anon_WITH_ARIA_256_GCM_SHA384", + 0xC05C: "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256", + 0xC05D: "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384", + 0xC05E: "TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256", + 0xC05F: "TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384", + 0xC060: "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256", + 0xC061: "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384", + 0xC062: "TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256", + 0xC063: "TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384", + 0xC064: "TLS_PSK_WITH_ARIA_128_CBC_SHA256", + 0xC065: "TLS_PSK_WITH_ARIA_256_CBC_SHA384", + 0xC066: "TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256", + 0xC067: "TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384", + 0xC068: "TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256", + 0xC069: "TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384", + 0xC06A: "TLS_PSK_WITH_ARIA_128_GCM_SHA256", + 0xC06B: "TLS_PSK_WITH_ARIA_256_GCM_SHA384", + 0xC06C: "TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256", + 0xC06D: "TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384", + 0xC06E: "TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256", + 0xC06F: "TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384", + 0xC070: "TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256", + 0xC071: "TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384", + 0xC072: "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", + 0xC073: "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", + 0xC074: "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", + 0xC075: "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", + 0xC076: "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", + 0xC077: "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", + 0xC078: "TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256", + 0xC079: "TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384", + 0xC07A: "TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256", + 0xC07B: "TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384", + 0xC07C: "TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256", + 0xC07D: "TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384", + 0xC07E: "TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256", + 0xC07F: "TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384", + 0xC080: "TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256", + 0xC081: "TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384", + 0xC082: "TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256", + 0xC083: "TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384", + 0xC084: "TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256", + 0xC085: "TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384", + 0xC086: "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", + 0xC087: "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", + 0xC088: "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", + 0xC089: "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", + 0xC08A: "TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256", + 0xC08B: "TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384", + 0xC08C: "TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256", + 0xC08D: "TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384", + 0xC08E: "TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256", + 0xC08F: "TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384", + 0xC090: "TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256", + 0xC091: "TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384", + 0xC092: "TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256", + 0xC093: "TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384", + 0xC094: "TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256", + 0xC095: "TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384", + 0xC096: "TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", + 0xC097: "TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", + 0xC098: "TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256", + 0xC099: "TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384", + 0xC09A: "TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", + 0xC09B: "TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", + 0xC09C: "TLS_RSA_WITH_AES_128_CCM", + 0xC09D: "TLS_RSA_WITH_AES_256_CCM", + 0xC09E: "TLS_DHE_RSA_WITH_AES_128_CCM", + 0xC09F: "TLS_DHE_RSA_WITH_AES_256_CCM", + 0xC0A0: "TLS_RSA_WITH_AES_128_CCM_8", + 0xC0A1: "TLS_RSA_WITH_AES_256_CCM_8", + 0xC0A2: "TLS_DHE_RSA_WITH_AES_128_CCM_8", + 0xC0A3: "TLS_DHE_RSA_WITH_AES_256_CCM_8", + 0xC0A4: "TLS_PSK_WITH_AES_128_CCM", + 0xC0A5: "TLS_PSK_WITH_AES_256_CCM", + 0xC0A6: "TLS_DHE_PSK_WITH_AES_128_CCM", + 0xC0A7: "TLS_DHE_PSK_WITH_AES_256_CCM", + 0xC0A8: "TLS_PSK_WITH_AES_128_CCM_8", + 0xC0A9: "TLS_PSK_WITH_AES_256_CCM_8", + 0xC0AA: "TLS_PSK_DHE_WITH_AES_128_CCM_8", + 0xC0AB: "TLS_PSK_DHE_WITH_AES_256_CCM_8", + 0xC0AC: "TLS_ECDHE_ECDSA_WITH_AES_128_CCM", + 0xC0AD: "TLS_ECDHE_ECDSA_WITH_AES_256_CCM", + 0xC0AE: "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8", + 0xC0AF: "TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8", + 0xC0B0: "TLS_ECCPWD_WITH_AES_128_GCM_SHA256", + 0xC0B1: "TLS_ECCPWD_WITH_AES_256_GCM_SHA384", + 0xC0B2: "TLS_ECCPWD_WITH_AES_128_CCM_SHA256", + 0xC0B3: "TLS_ECCPWD_WITH_AES_256_CCM_SHA384", + 0xC0B4: "TLS_SHA256_SHA256", + 0xC0B5: "TLS_SHA384_SHA384", + 0xC100: "TLS_GOSTR341112_256_WITH_KUZNYECHIK_CTR_OMAC", + 0xC101: "TLS_GOSTR341112_256_WITH_MAGMA_CTR_OMAC", + 0xC102: "TLS_GOSTR341112_256_WITH_28147_CNT_IMIT", + 0xC103: "TLS_GOSTR341112_256_WITH_KUZNYECHIK_MGM_L", + 0xC104: "TLS_GOSTR341112_256_WITH_MAGMA_MGM_L", + 0xC105: "TLS_GOSTR341112_256_WITH_KUZNYECHIK_MGM_S", + 0xC106: "TLS_GOSTR341112_256_WITH_MAGMA_MGM_S", + 0xCACA: "GREASE", + 0xCCA8: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + 0xCCA9: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + 0xCCAA: "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + 0xCCAB: "TLS_PSK_WITH_CHACHA20_POLY1305_SHA256", + 0xCCAC: "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256", + 0xCCAD: "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256", + 0xCCAE: "TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256", + 0xD001: "TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256", + 0xD002: "TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384", + 0xD003: "TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256", + 0xD005: "TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256", + 0xDADA: "GREASE", + 0xEAEA: "GREASE", + 0xFAFA: "GREASE", +}; + +/** + * GREASE values + */ +export const GREASE_VALUES = [ + 0x0a0a, + 0x1a1a, + 0x2a2a, + 0x3a3a, + 0x4a4a, + 0x5a5a, + 0x6a6a, + 0x7a7a, + 0x8a8a, + 0x9a9a, + 0xaaaa, + 0xbaba, + 0xcaca, + 0xdada, + 0xeaea, + 0xfafa +]; + +/** + * Parses the supported_versions extension and returns the highest supported version. + * @param {Uint8Array} bytes + * @returns {number} + */ +export function parseHighestSupportedVersion(bytes) { + const s = new Stream(bytes); + + // Length + let i = s.readInt(1); + + let highestVersion = 0; + while (s.hasMore() && i-- > 0) { + const v = s.readInt(2); + if (GREASE_VALUES.includes(v)) continue; + if (v > highestVersion) highestVersion = v; + } + + return highestVersion; +} + +/** + * Parses the application_layer_protocol_negotiation extension and returns the first value. + * @param {Uint8Array} bytes + * @returns {number} + */ +export function parseFirstALPNValue(bytes) { + const s = new Stream(bytes); + const alpnExtLen = s.readInt(2); + if (alpnExtLen < 3) return "00"; + const strLen = s.readInt(1); + if (strLen < 2) return "00"; + return s.readString(strLen); +} diff --git a/src/core/operations/BlowfishDecrypt.mjs b/src/core/operations/BlowfishDecrypt.mjs index f7dc8d17..43d6718a 100644 --- a/src/core/operations/BlowfishDecrypt.mjs +++ b/src/core/operations/BlowfishDecrypt.mjs @@ -70,10 +70,14 @@ class BlowfishDecrypt extends Operation { inputType = args[3], outputType = args[4]; - if (key.length !== 8) { + if (key.length < 4 || key.length > 56) { throw new OperationError(`Invalid key length: ${key.length} bytes -Blowfish uses a key length of 8 bytes (64 bits).`); +Blowfish's key length needs to be between 4 and 56 bytes (32-448 bits).`); + } + + if (iv.length !== 8) { + throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes`); } input = Utils.convertToByteString(input, inputType); diff --git a/src/core/operations/BlowfishEncrypt.mjs b/src/core/operations/BlowfishEncrypt.mjs index 2cf3672b..eab3d286 100644 --- a/src/core/operations/BlowfishEncrypt.mjs +++ b/src/core/operations/BlowfishEncrypt.mjs @@ -70,10 +70,14 @@ class BlowfishEncrypt extends Operation { inputType = args[3], outputType = args[4]; - if (key.length !== 8) { + if (key.length < 4 || key.length > 56) { throw new OperationError(`Invalid key length: ${key.length} bytes + +Blowfish's key length needs to be between 4 and 56 bytes (32-448 bits).`); + } -Blowfish uses a key length of 8 bytes (64 bits).`); + if (iv.length !== 8) { + throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes`); } input = Utils.convertToByteString(input, inputType); diff --git a/src/core/operations/CSSSelector.mjs b/src/core/operations/CSSSelector.mjs index d6b8da11..639726c4 100644 --- a/src/core/operations/CSSSelector.mjs +++ b/src/core/operations/CSSSelector.mjs @@ -6,7 +6,7 @@ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; -import xmldom from "xmldom"; +import xmldom from "@xmldom/xmldom"; import nwmatcher from "nwmatcher"; /** diff --git a/src/core/operations/ChaCha.mjs b/src/core/operations/ChaCha.mjs index 166c1663..2d186d35 100644 --- a/src/core/operations/ChaCha.mjs +++ b/src/core/operations/ChaCha.mjs @@ -100,7 +100,7 @@ class ChaCha extends Operation { super(); this.name = "ChaCha"; - this.module = "Default"; + this.module = "Ciphers"; this.description = "ChaCha is a stream cipher designed by Daniel J. Bernstein. It is a variant of the Salsa stream cipher. Several parameterizations exist; 'ChaCha' may refer to the original construction, or to the variant as described in RFC-8439. ChaCha is often used with Poly1305, in the ChaCha20-Poly1305 AEAD construction.

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

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

Counter: ChaCha uses a counter of 4 or 8 bytes (32 or 64 bits); together, the nonce and counter must add up to 16 bytes. The counter starts at zero at the start of the keystream, and is incremented at every 64 bytes."; this.infoURL = "https://wikipedia.org/wiki/Salsa20#ChaCha_variant"; this.inputType = "string"; @@ -191,7 +191,7 @@ ChaCha uses a nonce of 8 or 12 bytes (64 or 96 bits).`); if (outputType === "Hex") { return toHex(output); } else { - return Utils.arrayBufferToStr(output); + return Utils.arrayBufferToStr(Uint8Array.from(output).buffer); } } diff --git a/src/core/operations/DateTimeDelta.mjs b/src/core/operations/DateTimeDelta.mjs new file mode 100644 index 00000000..7f82cf01 --- /dev/null +++ b/src/core/operations/DateTimeDelta.mjs @@ -0,0 +1,107 @@ +/** + * @author tomgond [tom.gonda@gmail.com] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import moment from "moment-timezone"; +import {DATETIME_FORMATS, FORMAT_EXAMPLES} from "../lib/DateTime.mjs"; + +/** + * DateTime Delta operation + */ +class DateTimeDelta extends Operation { + + /** + * DateTimeDelta constructor + */ + constructor() { + super(); + + this.name = "DateTime Delta"; + this.module = "Default"; + this.description = "Calculates a new DateTime value given an input DateTime value and a time difference (delta) from the input DateTime value."; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + "name": "Built in formats", + "type": "populateOption", + "value": DATETIME_FORMATS, + "target": 1 + }, + { + "name": "Input format string", + "type": "binaryString", + "value": "DD/MM/YYYY HH:mm:ss" + }, + { + "name": "Time Operation", + "type": "option", + "value": ["Add", "Subtract"] + }, + { + "name": "Days", + "type": "number", + "value": 0 + }, + { + "name": "Hours", + "type": "number", + "value": 0 + }, + { + "name": "Minutes", + "type": "number", + "value": 0 + }, + { + "name": "Seconds", + "type": "number", + "value": 0 + } + + ]; + } + + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const inputTimezone = "UTC"; + const inputFormat = args[1]; + const operationType = args[2]; + const daysDelta = args[3]; + const hoursDelta = args[4]; + const minutesDelta = args[5]; + const secondsDelta = args[6]; + let date = ""; + + try { + date = moment.tz(input, inputFormat, inputTimezone); + if (!date || date.format() === "Invalid date") throw Error; + } catch (err) { + return `Invalid format.\n\n${FORMAT_EXAMPLES}`; + } + let newDate; + if (operationType === "Add") { + newDate = date.add(daysDelta, "days") + .add(hoursDelta, "hours") + .add(minutesDelta, "minutes") + .add(secondsDelta, "seconds"); + + } else { + newDate = date.add(-daysDelta, "days") + .add(-hoursDelta, "hours") + .add(-minutesDelta, "minutes") + .add(-secondsDelta, "seconds"); + } + return newDate.tz(inputTimezone).format(inputFormat.replace(/[<>]/g, "")); + } +} + +export default DateTimeDelta; diff --git a/src/core/operations/DeriveEVPKey.mjs b/src/core/operations/DeriveEVPKey.mjs index 5885f892..3d67aa51 100644 --- a/src/core/operations/DeriveEVPKey.mjs +++ b/src/core/operations/DeriveEVPKey.mjs @@ -62,11 +62,13 @@ class DeriveEVPKey extends Operation { * @returns {string} */ run(input, args) { - const passphrase = Utils.convertToByteString(args[0].string, args[0].option), + const passphrase = CryptoJS.enc.Latin1.parse( + Utils.convertToByteString(args[0].string, args[0].option)), keySize = args[1] / 32, iterations = args[2], hasher = args[3], - salt = Utils.convertToByteString(args[4].string, args[4].option), + salt = CryptoJS.enc.Latin1.parse( + Utils.convertToByteString(args[4].string, args[4].option)), key = CryptoJS.EvpKDF(passphrase, salt, { // lgtm [js/insufficient-password-hash] keySize: keySize, hasher: CryptoJS.algo[hasher], diff --git a/src/core/operations/ExtractHashes.mjs b/src/core/operations/ExtractHashes.mjs new file mode 100644 index 00000000..fd50089f --- /dev/null +++ b/src/core/operations/ExtractHashes.mjs @@ -0,0 +1,84 @@ +/** + * @author mshwed [m@ttshwed.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { search } from "../lib/Extract.mjs"; + +/** + * Extract Hash Values operation + */ +class ExtractHashes extends Operation { + + /** + * ExtractHashValues constructor + */ + constructor() { + super(); + + this.name = "Extract hashes"; + this.module = "Regex"; + this.description = "Extracts potential hashes based on hash character length"; + this.infoURL = "https://wikipedia.org/wiki/Comparison_of_cryptographic_hash_functions"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Hash character length", + type: "number", + value: 40 + }, + { + name: "All hashes", + type: "boolean", + value: false + }, + { + name: "Display Total", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const results = []; + let hashCount = 0; + + const [hashLength, searchAllHashes, showDisplayTotal] = args; + + // Convert character length to bit length + let hashBitLengths = [(hashLength / 2) * 8]; + + if (searchAllHashes) hashBitLengths = [4, 8, 16, 32, 64, 128, 160, 192, 224, 256, 320, 384, 512, 1024]; + + for (const hashBitLength of hashBitLengths) { + // Convert bit length to character length + const hashCharacterLength = (hashBitLength / 8) * 2; + + const regex = new RegExp(`(\\b|^)[a-f0-9]{${hashCharacterLength}}(\\b|$)`, "g"); + const searchResults = search(input, regex, null, false); + + hashCount += searchResults.length; + results.push(...searchResults); + } + + let output = ""; + if (showDisplayTotal) { + output = `Total Results: ${hashCount}\n\n`; + } + + output = output + results.join("\n"); + return output; + } + +} + +export default ExtractHashes; diff --git a/src/core/operations/FangURL.mjs b/src/core/operations/FangURL.mjs new file mode 100644 index 00000000..ad5bf525 --- /dev/null +++ b/src/core/operations/FangURL.mjs @@ -0,0 +1,78 @@ +/** + * @author arnydo [github@arnydo.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * FangURL operation + */ +class FangURL extends Operation { + + /** + * FangURL constructor + */ + constructor() { + super(); + + this.name = "Fang URL"; + this.module = "Default"; + this.description = "Takes a 'Defanged' Universal Resource Locator (URL) and 'Fangs' it. Meaning, it removes the alterations (defanged) that render it useless so that it can be used again."; + this.infoURL = "https://isc.sans.edu/forums/diary/Defang+all+the+things/22744/"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Restore [.]", + type: "boolean", + value: true + }, + { + name: "Restore hxxp", + type: "boolean", + value: true + }, + { + name: "Restore ://", + type: "boolean", + value: true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [dots, http, slashes] = args; + + input = fangURL(input, dots, http, slashes); + + return input; + } + +} + + +/** + * Defangs a given URL + * + * @param {string} url + * @param {boolean} dots + * @param {boolean} http + * @param {boolean} slashes + * @returns {string} + */ +function fangURL(url, dots, http, slashes) { + if (dots) url = url.replace(/\[\.\]/g, "."); + if (http) url = url.replace(/hxxp/g, "http"); + if (slashes) url = url.replace(/\[:\/\/\]/g, "://"); + + return url; +} + +export default FangURL; diff --git a/src/core/operations/FileTree.mjs b/src/core/operations/FileTree.mjs index 8321f8f5..9484313f 100644 --- a/src/core/operations/FileTree.mjs +++ b/src/core/operations/FileTree.mjs @@ -1,6 +1,6 @@ /** * @author sw5678 - * @copyright Crown Copyright 2016 + * @copyright Crown Copyright 2023 * @license Apache-2.0 */ @@ -21,7 +21,8 @@ class FileTree extends Operation { this.name = "File Tree"; this.module = "Default"; - this.description = "Creates file tree from list of file paths (similar to the tree command in Linux)"; + this.description = "Creates a file tree from a list of file paths (similar to the tree command in Linux)"; + this.infoURL = "https://wikipedia.org/wiki/Tree_(command)"; this.inputType = "string"; this.outputType = "string"; this.args = [ diff --git a/src/core/operations/FromBase58.mjs b/src/core/operations/FromBase58.mjs index f5a9ac3d..cb491159 100644 --- a/src/core/operations/FromBase58.mjs +++ b/src/core/operations/FromBase58.mjs @@ -60,7 +60,7 @@ class FromBase58 extends Operation { run(input, args) { let alphabet = args[0] || ALPHABET_OPTIONS[0].value; const removeNonAlphaChars = args[1] === undefined ? true : args[1], - result = [0]; + result = []; alphabet = Utils.expandAlphRange(alphabet).join(""); @@ -87,11 +87,9 @@ class FromBase58 extends Operation { } } - let carry = result[0] * 58 + index; - result[0] = carry & 0xFF; - carry = carry >> 8; + let carry = index; - for (let i = 1; i < result.length; i++) { + for (let i = 0; i < result.length; i++) { carry += result[i] * 58; result[i] = carry & 0xFF; carry = carry >> 8; diff --git a/src/core/operations/FromFloat.mjs b/src/core/operations/FromFloat.mjs new file mode 100644 index 00000000..8bf56d81 --- /dev/null +++ b/src/core/operations/FromFloat.mjs @@ -0,0 +1,78 @@ +/** + * @author tcode2k16 [tcode2k16@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import ieee754 from "ieee754"; +import {DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * From Float operation + */ +class FromFloat extends Operation { + + /** + * FromFloat constructor + */ + constructor() { + super(); + + this.name = "From Float"; + this.module = "Default"; + this.description = "Convert from IEEE754 Floating Point Numbers"; + this.infoURL = "https://wikipedia.org/wiki/IEEE_754"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Endianness", + "type": "option", + "value": [ + "Big Endian", + "Little Endian" + ] + }, + { + "name": "Size", + "type": "option", + "value": [ + "Float (4 bytes)", + "Double (8 bytes)" + ] + }, + { + "name": "Delimiter", + "type": "option", + "value": DELIM_OPTIONS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + if (input.length === 0) return []; + + const [endianness, size, delimiterName] = args; + const delim = Utils.charRep(delimiterName || "Space"); + const byteSize = size === "Double (8 bytes)" ? 8 : 4; + const isLE = endianness === "Little Endian"; + const mLen = byteSize === 4 ? 23 : 52; + const floats = input.split(delim); + + const output = new Array(floats.length*byteSize); + for (let i = 0; i < floats.length; i++) { + ieee754.write(output, parseFloat(floats[i]), i*byteSize, isLE, mLen, byteSize); + } + return output; + } + +} + +export default FromFloat; diff --git a/src/core/operations/JA4Fingerprint.mjs b/src/core/operations/JA4Fingerprint.mjs new file mode 100644 index 00000000..31f57525 --- /dev/null +++ b/src/core/operations/JA4Fingerprint.mjs @@ -0,0 +1,73 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {toJA4} from "../lib/JA4.mjs"; + +/** + * JA4 Fingerprint operation + */ +class JA4Fingerprint extends Operation { + + /** + * JA4Fingerprint constructor + */ + constructor() { + super(); + + this.name = "JA4 Fingerprint"; + this.module = "Crypto"; + this.description = "Generates a JA4 fingerprint to help identify TLS clients based on hashing together values from the Client Hello.

Input: A hex stream of the TLS or QUIC Client Hello packet application layer."; + this.infoURL = "https://medium.com/foxio/ja4-network-fingerprinting-9376fe9ca637"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Input format", + type: "option", + value: ["Hex", "Base64", "Raw"] + }, + { + name: "Output format", + type: "option", + value: ["JA4", "JA4 Original Rendering", "JA4 Raw", "JA4 Raw Original Rendering", "All"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [inputFormat, outputFormat] = args; + input = Utils.convertToByteArray(input, inputFormat); + const ja4 = toJA4(new Uint8Array(input)); + + // Output + switch (outputFormat) { + case "JA4": + return ja4.JA4; + case "JA4 Original Rendering": + return ja4.JA4_o; + case "JA4 Raw": + return ja4.JA4_r; + case "JA4 Raw Original Rendering": + return ja4.JA4_ro; + case "All": + default: + return `JA4: ${ja4.JA4} +JA4_o: ${ja4.JA4_o} +JA4_r: ${ja4.JA4_r} +JA4_ro: ${ja4.JA4_ro}`; + } + } + +} + +export default JA4Fingerprint; diff --git a/src/core/operations/MurmurHash3.mjs b/src/core/operations/MurmurHash3.mjs index f4aea611..7921633f 100644 --- a/src/core/operations/MurmurHash3.mjs +++ b/src/core/operations/MurmurHash3.mjs @@ -22,7 +22,7 @@ class MurmurHash3 extends Operation { super(); this.name = "MurmurHash3"; - this.module = "Default"; + this.module = "Hashing"; this.description = "Generates a MurmurHash v3 for a string input and an optional seed input"; this.infoURL = "https://wikipedia.org/wiki/MurmurHash"; this.inputType = "string"; @@ -115,11 +115,7 @@ class MurmurHash3 extends Operation { * @return {number} 32-bit signed integer */ unsignedToSigned(value) { - if (value & 0x80000000) { - return -0x100000000 + value; - } else { - return value; - } + return value & 0x80000000 ? -0x100000000 + value : value; } /** diff --git a/src/core/operations/ParseCSR.mjs b/src/core/operations/ParseCSR.mjs new file mode 100644 index 00000000..6ab44cb2 --- /dev/null +++ b/src/core/operations/ParseCSR.mjs @@ -0,0 +1,273 @@ +/** + * @author jkataja + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import forge from "node-forge"; +import Utils from "../Utils.mjs"; + +/** + * Parse CSR operation + */ +class ParseCSR extends Operation { + + /** + * ParseCSR constructor + */ + constructor() { + super(); + + this.name = "Parse CSR"; + this.module = "PublicKey"; + this.description = "Parse Certificate Signing Request (CSR) for an X.509 certificate"; + this.infoURL = "https://wikipedia.org/wiki/Certificate_signing_request"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Input format", + "type": "option", + "value": ["PEM"] + }, + { + "name": "Key type", + "type": "option", + "value": ["RSA"] + }, + { + "name": "Strict ASN.1 value lengths", + "type": "boolean", + "value": true + } + ]; + this.checks = [ + { + "pattern": "^-+BEGIN CERTIFICATE REQUEST-+\\r?\\n[\\da-z+/\\n\\r]+-+END CERTIFICATE REQUEST-+\\r?\\n?$", + "flags": "i", + "args": ["PEM"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} Human-readable description of a Certificate Signing Request (CSR). + */ + run(input, args) { + if (!input.length) { + return "No input"; + } + + const csr = forge.pki.certificationRequestFromPem(input, args[1]); + + // RSA algorithm is the only one supported for CSR in node-forge as of 1.3.1 + return `Version: ${1 + csr.version} (0x${Utils.hex(csr.version)}) +Subject${formatSubject(csr.subject)} +Subject Alternative Names${formatSubjectAlternativeNames(csr)} +Public Key + Algorithm: RSA + Length: ${csr.publicKey.n.bitLength()} bits + Modulus: ${formatMultiLine(chop(csr.publicKey.n.toString(16).replace(/(..)/g, "$&:")))} + Exponent: ${csr.publicKey.e} (0x${Utils.hex(csr.publicKey.e)}) +Signature + Algorithm: ${forge.pki.oids[csr.signatureOid]} + Signature: ${formatMultiLine(Utils.strToByteArray(csr.signature).map(b => Utils.hex(b)).join(":"))} +Extensions${formatExtensions(csr)}`; + } +} + +/** + * Format Subject of the request as a multi-line string + * @param {*} subject CSR Subject + * @returns Multi-line string describing Subject + */ +function formatSubject(subject) { + let out = "\n"; + + for (const attribute of subject.attributes) { + out += ` ${attribute.shortName} = ${attribute.value}\n`; + } + + return chop(out); +} + + +/** + * Format Subject Alternative Names from the name `subjectAltName` extension + * @param {*} extension CSR object + * @returns Multi-line string describing Subject Alternative Names + */ +function formatSubjectAlternativeNames(csr) { + let out = "\n"; + + for (const attribute of csr.attributes) { + for (const extension of attribute.extensions) { + if (extension.name === "subjectAltName") { + const names = []; + for (const altName of extension.altNames) { + switch (altName.type) { + case 1: + names.push(`EMAIL: ${altName.value}`); + break; + case 2: + names.push(`DNS: ${altName.value}`); + break; + case 6: + names.push(`URI: ${altName.value}`); + break; + case 7: + names.push(`IP: ${altName.ip}`); + break; + default: + names.push(`(unable to format type ${altName.type} name)\n`); + } + } + out += indent(2, names); + } + } + } + + return chop(out); +} + +/** + * Format known extensions of a CSR + * @param {*} csr CSR object + * @returns Multi-line string describing attributes + */ +function formatExtensions(csr) { + let out = "\n"; + + for (const attribute of csr.attributes) { + for (const extension of attribute.extensions) { + // formatted separately + if (extension.name === "subjectAltName") { + continue; + } + out += ` ${extension.name}${(extension.critical ? " CRITICAL" : "")}:\n`; + let parts = []; + switch (extension.name) { + case "basicConstraints" : + parts = describeBasicConstraints(extension); + break; + case "keyUsage" : + parts = describeKeyUsage(extension); + break; + case "extKeyUsage" : + parts = describeExtendedKeyUsage(extension); + break; + default : + parts = ["(unable to format extension)"]; + } + out += indent(4, parts); + } + } + + return chop(out); +} + + +/** + * Format hex string onto multiple lines + * @param {*} longStr + * @returns Hex string as a multi-line hex string + */ +function formatMultiLine(longStr) { + const lines = []; + + for (let remain = longStr ; remain !== "" ; remain = remain.substring(48)) { + lines.push(remain.substring(0, 48)); + } + + return lines.join("\n "); +} + +/** + * Describe Basic Constraints + * @see RFC 5280 4.2.1.9. Basic Constraints https://www.ietf.org/rfc/rfc5280.txt + * @param {*} extension CSR extension with the name `basicConstraints` + * @returns Array of strings describing Basic Constraints + */ +function describeBasicConstraints(extension) { + const constraints = []; + + constraints.push(`CA = ${extension.cA}`); + if (extension.pathLenConstraint !== undefined) constraints.push(`PathLenConstraint = ${extension.pathLenConstraint}`); + + return constraints; +} + +/** + * Describe Key Usage extension permitted use cases + * @see RFC 5280 4.2.1.3. Key Usage https://www.ietf.org/rfc/rfc5280.txt + * @param {*} extension CSR extension with the name `keyUsage` + * @returns Array of strings describing Key Usage extension permitted use cases + */ +function describeKeyUsage(extension) { + const usage = []; + + if (extension.digitalSignature) usage.push("Digital signature"); + if (extension.nonRepudiation) usage.push("Non-repudiation"); + if (extension.keyEncipherment) usage.push("Key encipherment"); + if (extension.dataEncipherment) usage.push("Data encipherment"); + if (extension.keyAgreement) usage.push("Key agreement"); + if (extension.keyCertSign) usage.push("Key certificate signing"); + if (extension.cRLSign) usage.push("CRL signing"); + if (extension.encipherOnly) usage.push("Encipher only"); + if (extension.decipherOnly) usage.push("Decipher only"); + + if (usage.length === 0) usage.push("(none)"); + + return usage; +} + +/** + * Describe Extended Key Usage extension permitted use cases + * @see RFC 5280 4.2.1.12. Extended Key Usage https://www.ietf.org/rfc/rfc5280.txt + * @param {*} extension CSR extension with the name `extendedKeyUsage` + * @returns Array of strings describing Extended Key Usage extension permitted use cases + */ +function describeExtendedKeyUsage(extension) { + const usage = []; + + if (extension.serverAuth) usage.push("TLS Web Server Authentication"); + if (extension.clientAuth) usage.push("TLS Web Client Authentication"); + if (extension.codeSigning) usage.push("Code signing"); + if (extension.emailProtection) usage.push("E-mail Protection (S/MIME)"); + if (extension.timeStamping) usage.push("Trusted Timestamping"); + if (extension.msCodeInd) usage.push("Microsoft Individual Code Signing"); + if (extension.msCodeCom) usage.push("Microsoft Commercial Code Signing"); + if (extension.msCTLSign) usage.push("Microsoft Trust List Signing"); + if (extension.msSGC) usage.push("Microsoft Server Gated Crypto"); + if (extension.msEFS) usage.push("Microsoft Encrypted File System"); + if (extension.nsSGC) usage.push("Netscape Server Gated Crypto"); + + if (usage.length === 0) usage.push("(none)"); + + return usage; +} + +/** + * Join an array of strings and add leading spaces to each line. + * @param {*} n How many leading spaces + * @param {*} parts Array of strings + * @returns Joined and indented string. + */ +function indent(n, parts) { + const fluff = " ".repeat(n); + return fluff + parts.join("\n" + fluff) + "\n"; +} + +/** + * Remove last character from a string. + * @param {*} s String + * @returns Chopped string. + */ +function chop(s) { + return s.substring(0, s.length - 1); +} + +export default ParseCSR; diff --git a/src/core/operations/RegularExpression.mjs b/src/core/operations/RegularExpression.mjs index 18d3fda9..9ea17e83 100644 --- a/src/core/operations/RegularExpression.mjs +++ b/src/core/operations/RegularExpression.mjs @@ -67,6 +67,10 @@ class RegularExpression extends Operation { name: "MAC address", value: "[A-Fa-f\\d]{2}(?:[:-][A-Fa-f\\d]{2}){5}" }, + { + name: "UUID", + value: "[0-9a-fA-F]{8}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{12}" + }, { name: "Date (yyyy-mm-dd)", value: "((?:19|20)\\d\\d)[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])" @@ -83,10 +87,6 @@ class RegularExpression extends Operation { name: "Strings", value: "[A-Za-z\\d/\\-:.,_$%\\x27\"()<>= !\\[\\]{}@]{4,}" }, - { - name: "UUID (any version)", - value: "[0-9a-fA-F]{8}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{12}" - }, ], "target": 1 }, diff --git a/src/core/operations/RisonDecode.mjs b/src/core/operations/RisonDecode.mjs index 1b9741a8..d4e36f80 100644 --- a/src/core/operations/RisonDecode.mjs +++ b/src/core/operations/RisonDecode.mjs @@ -20,7 +20,7 @@ class RisonDecode extends Operation { super(); this.name = "Rison Decode"; - this.module = "Default"; + this.module = "Encodings"; this.description = "Rison, a data serialization format optimized for compactness in URIs. Rison is a slight variation of JSON that looks vastly superior after URI encoding. Rison still expresses exactly the same set of data structures as JSON, so data can be translated back and forth without loss or guesswork."; this.infoURL = "https://github.com/Nanonid/rison"; this.inputType = "string"; @@ -29,11 +29,7 @@ class RisonDecode extends Operation { { name: "Decode Option", type: "editableOption", - value: [ - { name: "Decode", value: "Decode", }, - { name: "Decode Object", value: "Decode Object", }, - { name: "Decode Array", value: "Decode Array", }, - ] + value: ["Decode", "Decode Object", "Decode Array"] }, ]; } @@ -52,8 +48,9 @@ class RisonDecode extends Operation { return rison.decode_object(input); case "Decode Array": return rison.decode_array(input); + default: + throw new OperationError("Invalid Decode option"); } - throw new OperationError("Invalid Decode option"); } } diff --git a/src/core/operations/RisonEncode.mjs b/src/core/operations/RisonEncode.mjs index 36a61017..12b13b66 100644 --- a/src/core/operations/RisonEncode.mjs +++ b/src/core/operations/RisonEncode.mjs @@ -20,7 +20,7 @@ class RisonEncode extends Operation { super(); this.name = "Rison Encode"; - this.module = "Default"; + this.module = "Encodings"; this.description = "Rison, a data serialization format optimized for compactness in URIs. Rison is a slight variation of JSON that looks vastly superior after URI encoding. Rison still expresses exactly the same set of data structures as JSON, so data can be translated back and forth without loss or guesswork."; this.infoURL = "https://github.com/Nanonid/rison"; this.inputType = "Object"; @@ -28,13 +28,8 @@ class RisonEncode extends Operation { this.args = [ { name: "Encode Option", - type: "editableOption", - value: [ - { name: "Encode", value: "Encode", }, - { name: "Encode Object", value: "Encode Object", }, - { name: "Encode Array", value: "Encode Array", }, - { name: "Encode URI", value: "Encode URI", } - ] + type: "option", + value: ["Encode", "Encode Object", "Encode Array", "Encode URI"] }, ]; } @@ -55,8 +50,9 @@ class RisonEncode extends Operation { return rison.encode_array(input); case "Encode URI": return rison.encode_uri(input); + default: + throw new OperationError("Invalid encode option"); } - throw new OperationError("Invalid encode option"); } } diff --git a/src/core/operations/Salsa20.mjs b/src/core/operations/Salsa20.mjs new file mode 100644 index 00000000..7a76cf26 --- /dev/null +++ b/src/core/operations/Salsa20.mjs @@ -0,0 +1,154 @@ +/** + * @author joostrijneveld [joost@joostrijneveld.nl] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHex } from "../lib/Hex.mjs"; +import { salsa20Block } from "../lib/Salsa20.mjs"; + +/** + * Salsa20 operation + */ +class Salsa20 extends Operation { + + /** + * Salsa20 constructor + */ + constructor() { + super(); + + this.name = "Salsa20"; + this.module = "Ciphers"; + this.description = "Salsa20 is a stream cipher designed by Daniel J. Bernstein and submitted to the eSTREAM project; Salsa20/8 and Salsa20/12 are round-reduced variants. It is closely related to the ChaCha stream cipher.

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

Nonce: Salsa20 uses a nonce of 8 bytes (64 bits).

Counter: Salsa uses a counter of 8 bytes (64 bits). The counter starts at zero at the start of the keystream, and is incremented at every 64 bytes."; + this.infoURL = "https://wikipedia.org/wiki/Salsa20"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Nonce", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64", "Integer"] + }, + { + "name": "Counter", + "type": "number", + "value": 0, + "min": 0 + }, + { + "name": "Rounds", + "type": "option", + "value": ["20", "12", "8"] + }, + { + "name": "Input", + "type": "option", + "value": ["Hex", "Raw"] + }, + { + "name": "Output", + "type": "option", + "value": ["Raw", "Hex"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string, args[0].option), + nonceType = args[1].option, + rounds = parseInt(args[3], 10), + inputType = args[4], + outputType = args[5]; + + if (key.length !== 16 && key.length !== 32) { + throw new OperationError(`Invalid key length: ${key.length} bytes. + +Salsa20 uses a key of 16 or 32 bytes (128 or 256 bits).`); + } + + let counter, nonce; + if (nonceType === "Integer") { + nonce = Utils.intToByteArray(parseInt(args[1].string, 10), 8, "little"); + } else { + nonce = Utils.convertToByteArray(args[1].string, args[1].option); + if (!(nonce.length === 8)) { + throw new OperationError(`Invalid nonce length: ${nonce.length} bytes. + +Salsa20 uses a nonce of 8 bytes (64 bits).`); + } + } + counter = Utils.intToByteArray(args[2], 8, "little"); + + const output = []; + input = Utils.convertToByteArray(input, inputType); + + let counterAsInt = Utils.byteArrayToInt(counter, "little"); + for (let i = 0; i < input.length; i += 64) { + counter = Utils.intToByteArray(counterAsInt, 8, "little"); + const stream = salsa20Block(key, nonce, counter, rounds); + for (let j = 0; j < 64 && i + j < input.length; j++) { + output.push(input[i + j] ^ stream[j]); + } + counterAsInt++; + } + + if (outputType === "Hex") { + return toHex(output); + } else { + return Utils.arrayBufferToStr(Uint8Array.from(output).buffer); + } + } + + /** + * Highlight Salsa20 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + const inputType = args[4], + outputType = args[5]; + if (inputType === "Raw" && outputType === "Raw") { + return pos; + } + } + + /** + * Highlight Salsa20 in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + const inputType = args[4], + outputType = args[5]; + if (inputType === "Raw" && outputType === "Raw") { + return pos; + } + } + +} + +export default Salsa20; diff --git a/src/core/operations/ToBase58.mjs b/src/core/operations/ToBase58.mjs index 5353c40e..2e71b20e 100644 --- a/src/core/operations/ToBase58.mjs +++ b/src/core/operations/ToBase58.mjs @@ -43,7 +43,7 @@ class ToBase58 extends Operation { run(input, args) { input = new Uint8Array(input); let alphabet = args[0] || ALPHABET_OPTIONS[0].value, - result = [0]; + result = []; alphabet = Utils.expandAlphRange(alphabet).join(""); @@ -60,11 +60,9 @@ class ToBase58 extends Operation { } input.forEach(function(b) { - let carry = (result[0] << 8) + b; - result[0] = carry % 58; - carry = (carry / 58) | 0; + let carry = b; - for (let i = 1; i < result.length; i++) { + for (let i = 0; i < result.length; i++) { carry += result[i] << 8; result[i] = carry % 58; carry = (carry / 58) | 0; diff --git a/src/core/operations/ToFloat.mjs b/src/core/operations/ToFloat.mjs new file mode 100644 index 00000000..e0c70bc6 --- /dev/null +++ b/src/core/operations/ToFloat.mjs @@ -0,0 +1,80 @@ +/** + * @author tcode2k16 [tcode2k16@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import ieee754 from "ieee754"; +import {DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * To Float operation + */ +class ToFloat extends Operation { + + /** + * ToFloat constructor + */ + constructor() { + super(); + + this.name = "To Float"; + this.module = "Default"; + this.description = "Convert to IEEE754 Floating Point Numbers"; + this.infoURL = "https://wikipedia.org/wiki/IEEE_754"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + "name": "Endianness", + "type": "option", + "value": [ + "Big Endian", + "Little Endian" + ] + }, + { + "name": "Size", + "type": "option", + "value": [ + "Float (4 bytes)", + "Double (8 bytes)" + ] + }, + { + "name": "Delimiter", + "type": "option", + "value": DELIM_OPTIONS + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [endianness, size, delimiterName] = args; + const delim = Utils.charRep(delimiterName || "Space"); + const byteSize = size === "Double (8 bytes)" ? 8 : 4; + const isLE = endianness === "Little Endian"; + const mLen = byteSize === 4 ? 23 : 52; + + if (input.length % byteSize !== 0) { + throw new OperationError(`Input is not a multiple of ${byteSize}`); + } + + const output = []; + for (let i = 0; i < input.length; i+=byteSize) { + output.push(ieee754.read(input, i, isLE, mLen, byteSize)); + } + return output.join(delim); + } + +} + +export default ToFloat; diff --git a/src/core/operations/XPathExpression.mjs b/src/core/operations/XPathExpression.mjs index 7bfe3ee1..c850104b 100644 --- a/src/core/operations/XPathExpression.mjs +++ b/src/core/operations/XPathExpression.mjs @@ -6,7 +6,7 @@ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; -import xmldom from "xmldom"; +import xmldom from "@xmldom/xmldom"; import xpath from "xpath"; /** diff --git a/src/core/operations/XSalsa20.mjs b/src/core/operations/XSalsa20.mjs new file mode 100644 index 00000000..d289585b --- /dev/null +++ b/src/core/operations/XSalsa20.mjs @@ -0,0 +1,156 @@ +/** + * @author joostrijneveld [joost@joostrijneveld.nl] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHex } from "../lib/Hex.mjs"; +import { salsa20Block, hsalsa20 } from "../lib/Salsa20.mjs"; + +/** + * XSalsa20 operation + */ +class XSalsa20 extends Operation { + + /** + * XSalsa20 constructor + */ + constructor() { + super(); + + this.name = "XSalsa20"; + this.module = "Ciphers"; + this.description = "XSalsa20 is a variant of the Salsa20 stream cipher designed by Daniel J. Bernstein; XSalsa uses longer nonces.

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

Nonce: XSalsa20 uses a nonce of 24 bytes (192 bits).

Counter: XSalsa uses a counter of 8 bytes (64 bits). The counter starts at zero at the start of the keystream, and is incremented at every 64 bytes."; + this.infoURL = "https://en.wikipedia.org/wiki/Salsa20#XSalsa20_with_192-bit_nonce"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Nonce", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64", "Integer"] + }, + { + "name": "Counter", + "type": "number", + "value": 0, + "min": 0 + }, + { + "name": "Rounds", + "type": "option", + "value": ["20", "12", "8"] + }, + { + "name": "Input", + "type": "option", + "value": ["Hex", "Raw"] + }, + { + "name": "Output", + "type": "option", + "value": ["Raw", "Hex"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string, args[0].option), + nonceType = args[1].option, + rounds = parseInt(args[3], 10), + inputType = args[4], + outputType = args[5]; + + if (key.length !== 16 && key.length !== 32) { + throw new OperationError(`Invalid key length: ${key.length} bytes. + +XSalsa20 uses a key of 16 or 32 bytes (128 or 256 bits).`); + } + + let counter, nonce; + if (nonceType === "Integer") { + nonce = Utils.intToByteArray(parseInt(args[1].string, 10), 8, "little"); + } else { + nonce = Utils.convertToByteArray(args[1].string, args[1].option); + if (!(nonce.length === 24)) { + throw new OperationError(`Invalid nonce length: ${nonce.length} bytes. + +XSalsa20 uses a nonce of 24 bytes (192 bits).`); + } + } + counter = Utils.intToByteArray(args[2], 8, "little"); + + const xsalsaKey = hsalsa20(key, nonce.slice(0, 16), rounds); + + const output = []; + input = Utils.convertToByteArray(input, inputType); + + let counterAsInt = Utils.byteArrayToInt(counter, "little"); + for (let i = 0; i < input.length; i += 64) { + counter = Utils.intToByteArray(counterAsInt, 8, "little"); + const stream = salsa20Block(xsalsaKey, nonce.slice(16, 24), counter, rounds); + for (let j = 0; j < 64 && i + j < input.length; j++) { + output.push(input[i + j] ^ stream[j]); + } + counterAsInt++; + } + + if (outputType === "Hex") { + return toHex(output); + } else { + return Utils.arrayBufferToStr(Uint8Array.from(output).buffer); + } + } + + /** + * Highlight XSalsa20 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + const inputType = args[4], + outputType = args[5]; + if (inputType === "Raw" && outputType === "Raw") { + return pos; + } + } + + /** + * Highlight XSalsa20 in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + const inputType = args[4], + outputType = args[5]; + if (inputType === "Raw" && outputType === "Raw") { + return pos; + } + } + +} + +export default XSalsa20; diff --git a/src/core/operations/XXTEA.mjs b/src/core/operations/XXTEA.mjs new file mode 100644 index 00000000..1a0a4368 --- /dev/null +++ b/src/core/operations/XXTEA.mjs @@ -0,0 +1,182 @@ +/** + * @author devcydo [devcydo@gmail.com] + * @author Ma Bingyao [mabingyao@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {toBase64} from "../lib/Base64.mjs"; +import Utils from "../Utils.mjs"; + +/** + * XXTEA Encrypt operation + */ +class XXTEAEncrypt extends Operation { + + /** + * XXTEAEncrypt constructor + */ + constructor() { + super(); + + this.name = "XXTEA"; + this.module = "Default"; + this.description = "Corrected Block TEA (often referred to as XXTEA) is a block cipher designed to correct weaknesses in the original Block TEA. XXTEA operates on variable-length blocks that are some arbitrary multiple of 32 bits in size (minimum 64 bits). The number of full cycles depends on the block size, but there are at least six (rising to 32 for small block sizes). The original Block TEA applies the XTEA round function to each word in the block and combines it additively with its leftmost neighbour. Slow diffusion rate of the decryption process was immediately exploited to break the cipher. Corrected Block TEA uses a more involved round function which makes use of both immediate neighbours in processing each word in the block."; + this.infoURL = "https://wikipedia.org/wiki/XXTEA"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "string", + "value": "", + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let key = args[0]; + + if (input === undefined || input === null || input.length === 0) { + throw new OperationError("Invalid input length (0)"); + } + + if (key === undefined || key === null || key.length === 0) { + throw new OperationError("Invalid key length (0)"); + } + + input = Utils.convertToByteString(input, "utf8"); + key = Utils.convertToByteString(key, "utf8"); + + input = this.convertToUint32Array(input, true); + key = this.fixLength(this.convertToUint32Array(key, false)); + + let encrypted = this.encryptUint32Array(input, key); + + encrypted = toBase64(this.toBinaryString(encrypted, false)); + + return encrypted; + } + + /** + * Convert Uint32Array to binary string + * + * @param {Uint32Array} v + * @param {Boolean} includeLength + * @returns {string} + */ + toBinaryString(v, includeLENGTH) { + const LENGTH = v.length; + let n = LENGTH << 2; + if (includeLENGTH) { + const M = v[LENGTH - 1]; + n -= 4; + if ((M < n - 3) || (M > n)) { + return null; + } + n = M; + } + for (let i = 0; i < LENGTH; i++) { + v[i] = String.fromCharCode( + v[i] & 0xFF, + v[i] >>> 8 & 0xFF, + v[i] >>> 16 & 0xFF, + v[i] >>> 24 & 0xFF + ); + } + const RESULT = v.join(""); + if (includeLENGTH) { + return RESULT.substring(0, n); + } + return RESULT; + } + + /** + * @param {number} sum + * @param {number} y + * @param {number} z + * @param {number} p + * @param {number} e + * @param {number} k + * @returns {number} + */ + mx(sum, y, z, p, e, k) { + return ((z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4)) ^ ((sum ^ y) + (k[p & 3 ^ e] ^ z)); + } + + + /** + * Encrypt Uint32Array + * + * @param {Uint32Array} v + * @param {number} k + * @returns {Uint32Array} + */ + encryptUint32Array(v, k) { + const LENGTH = v.length; + const N = LENGTH - 1; + let y, z, sum, e, p, q; + z = v[N]; + sum = 0; + for (q = Math.floor(6 + 52 / LENGTH) | 0; q > 0; --q) { + sum = (sum + 0x9E3779B9) & 0xFFFFFFFF; + e = sum >>> 2 & 3; + for (p = 0; p < N; ++p) { + y = v[p + 1]; + z = v[p] = (v[p] + this.mx(sum, y, z, p, e, k)) & 0xFFFFFFFF; + } + y = v[0]; + z = v[N] = (v[N] + this.mx(sum, y, z, N, e, k)) & 0xFFFFFFFF; + } + return v; + } + + /** + * Fixes the Uint32Array lenght to 4 + * + * @param {Uint32Array} k + * @returns {Uint32Array} + */ + fixLength(k) { + if (k.length < 4) { + k.length = 4; + } + return k; + } + + /** + * Convert string to Uint32Array + * + * @param {string} bs + * @param {Boolean} includeLength + * @returns {Uint32Array} + */ + convertToUint32Array(bs, includeLength) { + const LENGTH = bs.length; + let n = LENGTH >> 2; + if ((LENGTH & 3) !== 0) { + ++n; + } + let v; + if (includeLength) { + v = new Array(n + 1); + v[n] = LENGTH; + } else { + v = new Array(n); + } + for (let i = 0; i < LENGTH; ++i) { + v[i >> 2] |= bs.charCodeAt(i) << ((i & 3) << 3); + } + return v; + } + +} + +export default XXTEAEncrypt; diff --git a/src/core/vendor/DisassembleX86-64.mjs b/src/core/vendor/DisassembleX86-64.mjs index 5f0ac65d..bbaf7a9b 100644 --- a/src/core/vendor/DisassembleX86-64.mjs +++ b/src/core/vendor/DisassembleX86-64.mjs @@ -4054,7 +4054,7 @@ function DecodeImmediate( type, BySize, SizeSetting ) //Sign bit adjust. - if( V32 >= ( n >> 1 ) ) { V32 -= n; } + if( V32 >= ( n / 2 ) ) { V32 -= n; } //Add position. diff --git a/src/web/App.mjs b/src/web/App.mjs index cce91b1e..53070e26 100755 --- a/src/web/App.mjs +++ b/src/web/App.mjs @@ -46,6 +46,8 @@ class App { this.appLoaded = false; this.workerLoaded = false; this.waitersLoaded = false; + + this.snackbars = []; } @@ -500,22 +502,22 @@ class App { // Input Character Encoding // Must be set before the input is loaded if (this.uriParams.ienc) { - this.manager.input.chrEncChange(parseInt(this.uriParams.ienc, 10)); + this.manager.input.chrEncChange(parseInt(this.uriParams.ienc, 10), true); } // Output Character Encoding if (this.uriParams.oenc) { - this.manager.output.chrEncChange(parseInt(this.uriParams.oenc, 10)); + this.manager.output.chrEncChange(parseInt(this.uriParams.oenc, 10), true); } // Input EOL sequence if (this.uriParams.ieol) { - this.manager.input.eolChange(this.uriParams.ieol); + this.manager.input.eolChange(this.uriParams.ieol, true); } // Output EOL sequence if (this.uriParams.oeol) { - this.manager.output.eolChange(this.uriParams.oeol); + this.manager.output.eolChange(this.uriParams.oeol, true); } // Read in input data from URI params @@ -708,14 +710,14 @@ class App { log.info("[" + time.toLocaleString() + "] " + str); if (silent) return; - this.currentSnackbar = $.snackbar({ + this.snackbars.push($.snackbar({ content: str, timeout: timeout, htmlAllowed: true, onClose: () => { - this.currentSnackbar.remove(); + this.snackbars.shift().remove(); } - }); + })); } diff --git a/src/web/html/index.html b/src/web/html/index.html index 5c3c3263..34c71d24 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -1,10 +1,10 @@ -
@@ -280,17 +280,17 @@ - - - - @@ -562,10 +562,10 @@