2
0
mirror of https://github.com/gchq/CyberChef synced 2025-12-27 13:43:30 +00:00

Compare commits

..

305 Commits

Author SHA1 Message Date
n1474335
95f7ed0de4 9.38.5 2022-06-03 22:13:50 +01:00
n1474335
6e7240026a Updated dependencies 2022-06-03 22:13:44 +01:00
n1474335
8bae7bf809 9.38.4 2022-06-03 21:41:44 +01:00
n1474335
b78bb2d3d6 Added 'Strict mode' to 'From Base64' operation 2022-06-03 21:41:37 +01:00
n1474335
f9a6402825 Merge branch 'strict_base64' of https://github.com/mikecat/CyberChef 2022-06-03 13:18:41 +01:00
n1474335
8ec5f3cb18 9.38.3 2022-06-03 13:15:25 +01:00
n1474335
c330394ff2 Fixed toBinary delim adjustment 2022-06-03 13:15:18 +01:00
n1474335
36e66ad5b4 Merge branch 'master' of https://github.com/michaellrowley/CyberChef 2022-06-03 13:10:59 +01:00
n1474335
a5a89efc06 9.38.2 2022-06-03 12:58:50 +01:00
n1474335
1078c37043 Merge branch 'master' of https://github.com/LukeSerne/CyberChef 2022-06-03 12:55:56 +01:00
n1474335
535c7188a8 9.38.1 2022-06-03 12:53:22 +01:00
n1474335
d6f9e216a6 Merge branch 'fix-to-base45' of https://github.com/mikecat/CyberChef 2022-06-03 12:50:45 +01:00
n1474335
7d6a879a67 Added script for updating the CHANGELOG 2022-05-31 00:20:19 +01:00
n1474335
668eac1f9e Fixed Split.js issues when resizing to around 1000px wide 2022-05-30 22:53:17 +01:00
n1474335
ff99436ce6 Fixed 'To Hex' highlighting 2022-05-30 19:43:59 +01:00
n1474335
ec577fc075 Fixed CSS for maximising output pane 2022-05-30 19:25:41 +01:00
n1474335
cc9d51b7be 9.38.0 2022-05-30 18:14:46 +01:00
n1474335
cf2b54e8c0 Update CHANGELOG 2022-05-30 18:14:41 +01:00
n1474335
a895d1d82a Added 'Parse TCP' operation 2022-05-30 18:06:15 +01:00
MikeCAT
11da4188ee fix "To Base45" ( #1351 ) 2022-05-20 11:59:04 +00:00
Luke Serné
5b68bad185 Support UTF8 encoded characters in Substitution operation
This adds support for UTF8-encoded characters in the input and the parameters.
2022-05-13 17:35:50 +02:00
n1474335
477e4a7421 9.37.3 2022-04-14 18:08:23 +01:00
n1474335
9a982f05ac Extract operations now offer built-in Sort and Unique options. Unique operation offers option to count occurances. Closes #1334. 2022-04-14 18:08:16 +01:00
n1474335
6959e2cf01 9.37.2 2022-04-14 16:57:51 +01:00
n1474335
f5fe79326a CodeQL fixes 2022-04-14 16:57:46 +01:00
n1474335
f77633cee9 9.37.1 2022-04-14 13:17:43 +01:00
n1474335
c858970573 Merge branch 'john19696-testui' 2022-04-14 13:17:32 +01:00
n1474335
fb3eceaee0 Tidied up UI tests 2022-04-14 13:16:36 +01:00
n1474335
8e37fec8f8 Merge branch 'testui' of https://github.com/john19696/CyberChef into john19696-testui 2022-04-14 11:55:56 +01:00
n1474335
ccaabfaee8 Fixed incorrect wording for RC4 Drop argument. 2022-04-14 11:55:34 +01:00
John L
c431fb30c5 added logging & jsdoc 2022-04-06 10:54:20 +01:00
John L
1c04848480 added log_path 2022-04-06 10:51:52 +01:00
John L
ca1a0797fb Merge branch 'master' into testui 2022-04-04 17:54:02 +01:00
John L
7b497181fd refactor samples, and more tests 2022-04-04 17:40:58 +01:00
John L
92767b1078 Try import 2022-03-31 19:32:41 +01:00
n1474335
7c66dacc40 9.37.0 2022-03-29 18:05:55 +01:00
n1474335
632277f9bd Updated CHANGELOG 2022-03-29 18:05:47 +01:00
n1474335
3c23ae03f5 Merge branch 'swesven-master' 2022-03-29 18:02:09 +01:00
n1474335
8117926ca3 Tidied up SM4 ops and NoPadding options for AES, DES and TripleDES 2022-03-29 18:01:57 +01:00
n1474335
31e9d27f1a Merge branch 'master' of https://github.com/swesven/CyberChef into swesven-master 2022-03-29 17:30:22 +01:00
n1474335
0c067d60d8 9.36.1 2022-03-29 17:26:17 +01:00
n1474335
1171d6b165 Merge branch 'pH-T-master' 2022-03-29 17:22:50 +01:00
n1474335
18022a2a48 Merge branch 'master' of https://github.com/pH-T/CyberChef into pH-T-master 2022-03-29 17:22:34 +01:00
n1474335
1400b5cca4 9.36.0 2022-03-29 12:48:47 +01:00
n1474335
98b5f9cc29 Updated CHANGELOG 2022-03-29 12:48:43 +01:00
n1474335
e766cb009e Merge branch 'hettysymes-SIGABA' 2022-03-29 12:45:56 +01:00
n1474335
993e276858 Tidied up Bletchley ops 2022-03-29 12:45:42 +01:00
n1474335
a762fb4df4 Merge branch 'SIGABA' of https://github.com/hettysymes/CyberChef into hettysymes-SIGABA 2022-03-29 12:26:39 +01:00
n1474335
00781fa459 Fixed lint 2022-03-29 11:55:41 +01:00
n1474335
c6e7367a4e 9.35.1 2022-03-28 17:15:51 +01:00
n1474335
3749bb1222 Merge branch 'AlexAndHisScripts-master' 2022-03-28 17:15:39 +01:00
n1474335
f3c83b2009 Merge branch 'master' of https://github.com/AlexAndHisScripts/CyberChef into AlexAndHisScripts-master 2022-03-28 17:14:57 +01:00
n1474335
d3583c31bc 9.35.0 2022-03-28 16:19:55 +01:00
n1474335
2f638d8c0c Updated CHANGELOG 2022-03-28 16:19:51 +01:00
n1474335
74e43ff65a Merge branch 't-8ch-base45' 2022-03-28 16:18:02 +01:00
n1474335
4f0b160ed3 Tidied up Base45 ops 2022-03-28 16:15:37 +01:00
n1474335
709b8696fc Merge branch 'base45' of https://github.com/t-8ch/CyberChef into t-8ch-base45 2022-03-28 16:04:34 +01:00
n1474335
e080c5d72e 9.34.2 2022-03-28 15:57:00 +01:00
n1474335
9cc177a9ad Code quality improvements 2022-03-28 15:56:15 +01:00
Paul Hager
9ad4e2525e fix: GetAllCasings.mjs test 2022-03-28 16:52:59 +02:00
n1474335
60b5fe0e76 9.34.1 2022-03-28 15:42:16 +01:00
n1474335
9273f97d88 Updated dependencies 2022-03-28 15:42:11 +01:00
Paul Hager
e2b7ac68ef fix: GetAllCasings.mjs - newline bug 2022-03-28 14:46:41 +02:00
n1474335
98a6baf55a 9.34.0 2022-03-28 11:40:04 +01:00
n1474335
b5677387f5 Updated CHANGELOG 2022-03-28 11:39:57 +01:00
n1474335
cd3eaa4762 Merge branch 'n1073645-variationsOfString' 2022-03-28 11:37:42 +01:00
n1474335
9733bf65de Merge branch 'nodejs16' of https://github.com/john19696/CyberChef into john19696-nodejs16 2022-03-28 11:37:23 +01:00
n1474335
bc433f0234 9.33.1 2022-03-28 11:23:19 +01:00
n1474335
ad7283ee6f Merge branch 'Gitoffthelawn-patch-1' 2022-03-28 11:21:49 +01:00
n1474335
5b941358a9 Merge branch 'patch-1' of https://github.com/Gitoffthelawn/CyberChef into Gitoffthelawn-patch-1 2022-03-28 11:21:22 +01:00
n1474335
f19e898c57 Merge branch 'Wingless-Archangel-patch-1' 2022-03-28 11:20:29 +01:00
n1474335
590ffa184d Merge branch 'patch-1' of https://github.com/Wingless-Archangel/CyberChef into Wingless-Archangel-patch-1 2022-03-28 11:20:10 +01:00
n1474335
46de51512f Merge branch 'adamkdean-patch-1' 2022-03-28 11:15:48 +01:00
n1474335
a13f2f26e4 Merge branch 'patch-1' of https://github.com/adamkdean/CyberChef into adamkdean-patch-1 2022-03-28 11:15:28 +01:00
n1474335
c9c26f6f9f Merge branch 'eljeffeg-patch-1' 2022-03-28 11:13:44 +01:00
n1474335
787c29e42b Merge branch 'patch-1' of https://github.com/eljeffeg/CyberChef into eljeffeg-patch-1 2022-03-28 11:13:26 +01:00
n1474335
1c0b83d833 9.33.0 2022-03-28 10:58:30 +01:00
n1474335
5c767d09b0 Updated CHANGELOG 2022-03-28 10:58:25 +01:00
n1474335
75dba51f56 Improve CJS and ESM module support #1037 2022-03-28 10:52:28 +01:00
n1474335
78a1827af8 Merge branch 'john19696-nodejs16' 2022-03-25 18:33:22 +00:00
n1474335
9e3733b33b Fixed Node imports 2022-03-25 18:28:01 +00:00
n1474335
4ef65589e8 Actions can now be triggered manually 2022-03-25 15:24:21 +00:00
n1474335
cf9e670309 Updated eslint 2022-03-25 15:17:00 +00:00
n1474335
b09f98fbb4 Updated to Node 17 2022-03-25 14:59:54 +00:00
n1474335
e43e010163 Merge branch 'nodejs16' of https://github.com/john19696/CyberChef into john19696-nodejs16 2022-03-25 13:26:31 +00:00
n1073645
2a5cee0bd3 9.32.4 2022-03-23 09:31:17 +00:00
n1073645
c962bb79f5 Updated Dependencies 2022-03-23 09:28:32 +00:00
John L
add745551b WIP 2022-02-11 16:56:58 +00:00
john19696
7b8213e1f6 Merge pull request #3 from john19696/nodejs16
Nodejs16 upgrade
2022-02-11 13:04:49 +00:00
John L
2e23a33dfc Merge branch 'nodejs16' of https://github.com/john19696/CyberChef into nodejs16 2022-02-04 11:03:05 +00:00
John L
bca296ee37 GitHub actions update 2022-02-04 11:02:52 +00:00
John L
2dbd647868 nodeFlags needs quote change 2022-01-31 11:39:17 +00:00
john19696
2991e7d1fe Update Gruntfile.js
add nodeFlags
2022-01-31 10:31:19 +00:00
John L
f6f12fc193 chromedriver update 2022-01-27 17:18:31 +00:00
john19696
1fac8c1cea Merge pull request #1 from t-8ch/nodejs16
Node 16 compatibility
2022-01-21 15:05:16 +00:00
Adam K Dean
590462e2e4 Fixed Crown Copyright link
Closes #1310
2022-01-18 22:07:59 +00:00
Michael Rowley
ed542582f9 Added more error-handling to ToUpperCase() 2021-12-29 19:59:48 +00:00
Michael Rowley
2574a63975 Minor adjustments 2021-12-29 19:32:39 +00:00
ElJeffe
291c55befd Spelling
Someone noticed this in another project that includes CyberChef
2021-12-14 08:47:55 -05:00
Thomas Weißschuh
7db1f39473 base45: Implement highlighting 2021-11-09 21:12:49 +01:00
Thomas Weißschuh
6017578964 Add Base45 operations
Closes #1219

Co-developed-by: Cyril Delétré <cyril.deletre@gmail.com>
2021-11-09 21:12:44 +01:00
Alex Chambers_Jones
d2174725a9 Fixed reflected XSS described in issue 1265 2021-10-29 17:59:02 +01:00
Wingless-Archangel
f630c499d5 Update .gitignore
add .node-version file for supporting [nodeenv](https://github.com/ekalinin/nodeenv)
2021-10-29 14:41:35 +02:00
MikeCAT
a7cdb095d2 Added input validation to fromBase64() 2021-10-04 22:39:16 +09:00
Thomas Weißschuh
cfc29ef821 Always use mjs imports
This is needed for Node/NPM 16 compat
2021-09-17 08:48:04 +02:00
Thomas Weißschuh
83c6775038 Support json modules
This is need for builds on Node/NPM 16
2021-09-17 08:45:56 +02:00
n1474335
ae1b12c120 9.32.3 2021-09-03 14:58:53 +01:00
n1474335
c423de545f Switch XOR input and output differential logic. Fixes #1155 2021-09-03 14:58:48 +01:00
n1474335
84011371b7 9.32.2 2021-08-26 16:51:50 +01:00
n1474335
f831ec6b7e Fixed issues in Protobuf parsing 2021-08-26 16:51:42 +01:00
n1474335
05bfa9158d 9.32.1 2021-08-19 12:06:30 +01:00
n1474335
649016bc85 Updated dependencies 2021-08-19 12:06:26 +01:00
n1474335
7492b874cf 9.32.0 2021-08-18 17:23:43 +01:00
n1474335
9ea21af61f Updated CHANGELOG 2021-08-18 17:23:38 +01:00
n1474335
dd18e52993 Protobuf operations improved to enable full and partial schema support 2021-08-18 17:22:09 +01:00
Gitoffthelawn
7712ee7f35 Improved consistency
Changed "or" to "and" because all the other sentence sections use "and".
2021-08-11 01:55:47 -07:00
n1474335
a4a13666e6 9.31.0 2021-08-10 16:51:28 +01:00
n1474335
07ef4da892 Updated CHANGELOG 2021-08-10 16:50:59 +01:00
n1474335
e9ca4dc9ca Added HASSH operations 2021-08-10 16:48:35 +01:00
n1474335
57bb8fbc45 9.30.0 2021-08-10 15:00:10 +01:00
n1474335
9175624210 Updated CHANGELOG 2021-08-10 15:00:04 +01:00
n1474335
289a417dfb Added 'JA3S Fingerprint' operation 2021-08-10 14:57:34 +01:00
n1474335
8379a9b275 Skipping UI tests in GitHub Actions 2021-08-10 14:26:33 +01:00
n1474335
5b1fad118f Fixed chromedriver path 2021-07-28 15:56:01 +01:00
n1474335
5e8985810e 9.29.2 2021-07-28 15:35:29 +01:00
n1474335
d2568e2a29 Updated dependencies 2021-07-28 15:35:24 +01:00
n1474335
6dfc21ef06 9.29.1 2021-07-28 14:58:17 +01:00
n1474335
1f19f2f58c Updated chromedriver 2021-07-28 14:58:09 +01:00
n1474335
1728cc7a85 9.29.0 2021-07-28 14:37:05 +01:00
n1474335
fa2fc2ba33 Updated CHANGELOG 2021-07-28 14:36:14 +01:00
n1474335
9a33498fed Added 'TLS JA3 Fingerprint' operation 2021-07-28 14:32:39 +01:00
n1474335
a3b873fd96 9.28.0 2021-03-26 14:09:51 +00:00
n1474335
97bd03799e Updated CHANGELOG 2021-03-26 14:09:37 +00:00
n1474335
ffaaaae2b4 Merge branch 'Danh4-issue-991' 2021-03-26 14:07:18 +00:00
n1474335
ff88d30d2f Tidied up CBOR operations 2021-03-26 14:07:02 +00:00
n1474335
88e3c2ccb2 Merge branch 'issue-991' of https://github.com/Danh4/CyberChef into Danh4-issue-991 2021-03-26 13:59:16 +00:00
swesven
6155634d3b Add the SM4 block cipher, also a no-padding option for block ciphers.
This adds an implementation of the SM4 block cipher, and operations
to encrypt and decrypt using it with CBC,ECB,CFB,OFB,CTR modes.

Also, a "no padding" option is added for AES,DES,3DES and SM4
decryption in ECB/CBC modes. This variant does not attempt to
validate the last block as being PKCS#7 padded.

This is useful, both since other padding schemes exist, and also
for decrypting data where the final block is missing.
2021-03-24 00:58:54 +01:00
n1474335
5029356514 Added link to FAQ description about output handling 2021-03-05 10:50:38 +00:00
n1474335
e57d5a7e75 9.27.6 2021-02-23 15:11:22 +00:00
n1474335
2bbe54cdcd Added further deconstruction of IPv6 Multicast Addresses in the 'Parse IPv6 Address' operation 2021-02-23 15:11:16 +00:00
n1474335
0e2423c390 9.27.5 2021-02-22 19:33:56 +00:00
n1474335
8fadad5891 AES Additional data can now be entered in a range of formats. #1011 2021-02-22 19:33:52 +00:00
n1474335
32455cd20f 9.27.4 2021-02-22 19:13:47 +00:00
n1474335
1e0e7f16a7 Added numeric validation for arguments in Binary and Hex operattions. Fixes #1178 2021-02-22 19:13:38 +00:00
n1474335
95884d77cf Extractable file formats are now listed properly in the 'Extract Files' description 2021-02-17 15:01:42 +00:00
n1474335
b69373f5e7 Fixed 'JSON to CSV' data flattening. 2021-02-16 14:48:56 +00:00
n1474335
61e85474d3 9.27.3 2021-02-16 14:36:36 +00:00
n1474335
3a9bdc58af Fixed 'JSON to CSV' handling of complex structures. Closes #637 2021-02-16 14:36:31 +00:00
n1474335
59c1c45d78 Updated dependencies 2021-02-16 14:17:09 +00:00
n1474335
b5f6cedd30 9.27.2 2021-02-16 14:12:18 +00:00
n1474335
c879af6860 Fixed 'Save recipe' URL generation issue. Closes #1176 2021-02-16 14:12:14 +00:00
n1474335
22fe5a6ae7 9.27.1 2021-02-12 17:55:36 +00:00
n1474335
57714c86a6 Escape HTML input in Fuzzy Match operation 2021-02-12 17:55:28 +00:00
n1474335
70cd375049 9.27.0 2021-02-12 13:54:52 +00:00
n1474335
e27e1dd42f Updated CHANGELOG 2021-02-12 13:53:59 +00:00
n1474335
8ad18bc7db Added 'Fuzzy Match' operation 2021-02-12 13:51:51 +00:00
n1474335
5893ac1a37 9.26.3 2021-02-12 12:12:08 +00:00
n1474335
83c3ab97f9 Merge branch 'n1073645-base64Alphabets' 2021-02-12 12:11:53 +00:00
n1474335
9b6be140fa Merge branch 'base64Alphabets' of https://github.com/n1073645/CyberChef into n1073645-base64Alphabets 2021-02-12 12:08:56 +00:00
n1474335
a6a60392c2 9.26.2 2021-02-12 12:05:03 +00:00
n1474335
ccfa0b991e Updated dependencies 2021-02-12 12:04:59 +00:00
n1474335
73b0e68993 Added code quality badge to README 2021-02-12 11:54:54 +00:00
n1474335
31a4eef001 9.26.1 2021-02-11 19:06:58 +00:00
n1474335
d6e2c9a6b9 Merge branch 'n1073645-HexdumpAsciiFix' 2021-02-11 19:06:47 +00:00
n1474335
e069f5db13 Tidied up hexdump UNIX format 2021-02-11 19:06:35 +00:00
n1474335
96b59cf0df Merge branch 'HexdumpAsciiFix' of https://github.com/n1073645/CyberChef into n1073645-HexdumpAsciiFix 2021-02-11 18:59:51 +00:00
n1474335
c1e1d4b7e3 9.26.0 2021-02-11 18:50:09 +00:00
n1474335
32d869231e Updated CHANGELOG 2021-02-11 18:50:03 +00:00
n1474335
6f95f01dda Merge branch 'n1073645-EPOCH' 2021-02-11 18:47:59 +00:00
n1474335
61a1c44f26 Renamed 'Generate Current Epoch' to 'Get Time'. It now uses the W3C High Resolution Time API and supports microsecond granularity 2021-02-11 18:47:44 +00:00
n1474335
e6c7899569 Merge branch 'EPOCH' of https://github.com/n1073645/CyberChef into n1073645-EPOCH 2021-02-11 18:08:55 +00:00
n1474335
a74a14145e 9.25.0 2021-02-11 18:03:45 +00:00
n1474335
04022b22be Updated CHANGELOG 2021-02-11 18:03:40 +00:00
n1474335
4f3010691c Merge branch 'n1073645-ID3Tags' 2021-02-11 18:01:24 +00:00
n1474335
672b477751 Extract ID3 operation now returns a JSON blob and presents an HTML table 2021-02-11 18:01:08 +00:00
n1474335
19360391a6 Merge branch 'ID3Tags' of https://github.com/n1073645/CyberChef into n1073645-ID3Tags 2021-02-11 16:16:33 +00:00
n1474335
447be8af34 9.24.8 2021-02-11 16:14:48 +00:00
n1474335
0989550e5c Merge branch 'n1073645-keychainExtractor' 2021-02-11 16:14:34 +00:00
n1474335
4d9b48b4d8 Tidied whitespace 2021-02-11 16:14:23 +00:00
n1474335
979652387d Merge branch 'keychainExtractor' of https://github.com/n1073645/CyberChef into n1073645-keychainExtractor 2021-02-11 16:12:57 +00:00
n1474335
de84fbdd1c Renamed workflows 2021-02-10 18:37:46 +00:00
n1474335
170e564319 Fixed incomplete multi-character sanitization and incomplete URL substring sanitization issues. 2021-02-10 17:41:39 +00:00
n1474335
530836876f 9.24.7 2021-02-10 13:13:25 +00:00
n1474335
1abc46058c Added a CodeQL workflow to check for bugs through code analysis. Fixed numerous bugs and implemented safeguards as already reported. 2021-02-10 13:13:19 +00:00
n1474335
892a3716ed Added Crypt lib for common resources 2021-02-09 15:00:35 +00:00
n1474335
5d982a9c8d 9.24.6 2021-02-09 14:50:17 +00:00
n1474335
6206224f1e Merge branch 'alblue-master' 2021-02-09 14:46:19 +00:00
n1474335
766310e2c7 Frequency Distribution operation now displays an HTML table 2021-02-09 14:46:04 +00:00
n1474335
02a397d2ae Merge branch 'master' of https://github.com/alblue/CyberChef into alblue-master 2021-02-09 14:30:03 +00:00
n1474335
4563c86acd 9.24.5 2021-02-09 14:24:08 +00:00
n1474335
0a59f8068e Merge branch 'aussieklutz-master' 2021-02-09 14:23:18 +00:00
n1474335
24548e3a48 Tidied up JWT tests 2021-02-09 14:23:02 +00:00
n1474335
f4784d49e7 Merge branch 'master' of https://github.com/aussieklutz/CyberChef into aussieklutz-master 2021-02-09 14:16:36 +00:00
n1474335
14d5069c6e Merge branch 'mt3571-1073-jwt-verify' 2021-02-09 14:15:12 +00:00
n1474335
9fdd55c5c6 Tidied up JWT ops 2021-02-09 14:14:59 +00:00
n1474335
5bc523aeff Merge branch '1073-jwt-verify' of https://github.com/mt3571/CyberChef into mt3571-1073-jwt-verify 2021-02-09 14:02:21 +00:00
n1474335
3ae2e2e2c8 Fixed highlighting of op names where only the description has hit 2021-02-09 11:50:20 +00:00
n1474335
83e49da7f6 Fixed description hiighlighting issue 2021-02-09 11:37:25 +00:00
n1474335
fe6df8778f Fixed year in CHANGELOG records. Closes #1168 2021-02-09 11:26:00 +00:00
Alex Blewitt
69073c9d99 Add ASCII output for frequency table
When showing results in the frequency distribution table, it's quite
helpful to see the distribution of the ASCII letters, if any. Provide an
option to show this to show the characters alongside the code.

Fixes #1169.

Signed-off-by: Alex Blewitt <alex.blewitt@gmail.com>
2021-02-07 16:22:13 +00:00
aussieklutz
d5a0adea0c Update JWTVerify.mjs 2021-02-06 18:35:46 +10:00
aussieklutz
1bcb8e433d Update JWTVerify.mjs 2021-02-06 18:10:54 +10:00
aussieklutz
fa05cf1d78 Update JWTVerify.mjs
Enabled ESRSA verification.
2021-02-06 17:58:49 +10:00
aussieklutz
63dff0d34d Update JWTVerify.mjs
Enabled validation of ECSHA256 JWT tokens in the tests
2021-02-06 17:55:44 +10:00
aussieklutz
e228b197f9 Update JWTVerify.mjs 2021-02-06 17:45:42 +10:00
aussieklutz
4bbeb6caa3 Update JWTVerify.mjs
Add expectation for working RSASHA256 test, and comment out unused privatekey.
2021-02-06 17:42:42 +10:00
aussieklutz
139d25dff9 Update JWTVerify.mjs
Update RSASHA256 test with the public key derived from the pre-existing private key, and expect a working testcase.
2021-02-06 17:40:04 +10:00
aussieklutz
6984258404 Update JWTVerify.mjs
Enable verification of RSASHA256 and 512 tokens
2021-02-06 17:27:54 +10:00
n1474335
ba66fd6546 Fixed recursive matching arguments 2021-02-05 19:04:27 +00:00
n1474335
47bbefd81f Fixed recursive scoring results in fuzzy match lib 2021-02-05 18:24:15 +00:00
n1474335
50f796049c Fixed search test 2021-02-05 18:07:20 +00:00
n1474335
618da545b1 9.24.4 2021-02-05 17:55:10 +00:00
n1474335
21236f1938 Added fuzzy search for operations 2021-02-05 17:54:57 +00:00
n1474335
4169a15066 9.24.3 2021-02-03 19:07:46 +00:00
n1474335
6b10f61e11 Moved postinstall script to a Grunt task to fix relative calls. npm scripts now use local grunt install. 2021-02-03 19:07:39 +00:00
n1474335
83f119f7e4 9.24.2 2021-02-03 17:54:57 +00:00
n1474335
041c899a35 Comments are now treated as disabled so that they do not interfere with the Dish type. Closes #1126 and #1132. Thanks to @mt3571 for the suggestion. 2021-02-03 17:54:49 +00:00
n1474335
5412fc01b3 Merge branch 'Prinzhorn-base_64_order' 2021-02-02 17:36:23 +00:00
n1474335
76926d9252 Merge branch 'base_64_order' of https://github.com/Prinzhorn/CyberChef into Prinzhorn-base_64_order 2021-02-02 17:36:10 +00:00
n1474335
3270961574 Merge branch 'n1073645-microsoftDecoderMagic' 2021-02-02 17:29:41 +00:00
n1474335
9a1ef71aec Merge branch 'microsoftDecoderMagic' of https://github.com/n1073645/CyberChef into n1073645-microsoftDecoderMagic 2021-02-02 17:29:25 +00:00
n1474335
ba878925ad Merge branch 'Prinzhorn-boolean_args' 2021-02-02 17:23:23 +00:00
n1474335
8d6b71bfaa Merge branch 'boolean_args' of https://github.com/Prinzhorn/CyberChef into Prinzhorn-boolean_args 2021-02-02 17:23:05 +00:00
n1474335
b6845aa03c 9.24.1 2021-02-02 17:18:49 +00:00
n1474335
4a673bd92a AES Decrypt now supports Additional Authenticated Data in GCM mode. Added tests for ADD at each AES size. 2021-02-02 17:18:35 +00:00
n1474335
fdffabfdd4 Merge branch 'n1073645-AESGCMAAD' 2021-02-02 16:11:03 +00:00
n1474335
ba8591293b Merge branch 'AESGCMAAD' of https://github.com/n1073645/CyberChef into n1073645-AESGCMAAD 2021-02-02 16:10:47 +00:00
n1474335
9b3aae10cf 9.24.0 2021-02-02 16:07:57 +00:00
n1474335
5c85c4df63 Updated CHANGELOG 2021-02-02 16:07:52 +00:00
n1474335
8ece2603fb Merge branch 'n1073645-SM3' 2021-02-02 16:06:49 +00:00
n1474335
1b54584820 Tweaks to various hashing functions to improve config options 2021-02-02 16:06:37 +00:00
n1474335
3ce3866000 Merge branch 'SM3' of https://github.com/n1073645/CyberChef into n1073645-SM3 2021-02-02 11:58:36 +00:00
n1474335
becc258b6c Updated CHANGELOG 2021-02-02 11:12:52 +00:00
n1474335
16c9e7119d Updated CHANGELOG 2021-02-02 11:02:23 +00:00
n1474335
f5888fea9c 9.23.1 2021-02-01 19:29:47 +00:00
n1474335
b5162c7549 Merge branch 'maqifrnswa-master' 2021-02-01 19:27:42 +00:00
n1474335
1baea1da3d Merge branch 'master' of https://github.com/maqifrnswa/CyberChef into maqifrnswa-master 2021-02-01 19:27:24 +00:00
n1474335
40899a6fe4 9.23.0 2021-02-01 19:16:03 +00:00
n1474335
66c533431d Merge branch 'mattnotmitt-rsa' 2021-02-01 19:15:46 +00:00
n1474335
74ae77f17a Tidied up and added tests for RSA operations 2021-02-01 19:15:32 +00:00
n1474335
99eb1cced5 Merge branch 'rsa' of https://github.com/mattnotmitt/CyberChef into mattnotmitt-rsa 2021-02-01 17:30:02 +00:00
n1474335
c880ecf3c4 Merge branch 'stephengroat-nvmrc' 2021-02-01 17:01:44 +00:00
n1474335
c54c34d88e Merge branch 'nvmrc' of https://github.com/stephengroat/CyberChef into stephengroat-nvmrc 2021-02-01 17:01:31 +00:00
n1474335
60b3c597a7 9.22.4 2021-02-01 16:57:48 +00:00
n1474335
372ab32539 Updated dependencies 2021-02-01 16:57:42 +00:00
n1474335
e14745a973 9.22.3 2021-02-01 16:43:19 +00:00
n1474335
afffe584cf Added flat lib for JSON to CSV op 2021-02-01 16:41:54 +00:00
n1474335
cf532f1e30 Merge branch 'n1073645-JSONTOCSV' 2021-02-01 16:35:09 +00:00
n1474335
46425ba552 Merge branch 'JSONTOCSV' of https://github.com/n1073645/CyberChef into n1073645-JSONTOCSV 2021-02-01 16:34:57 +00:00
n1474335
9f65fac4e6 Added actions for linting and testing Pull Requests 2021-02-01 16:34:12 +00:00
n1474335
af98feff51 Improved PGP keygen test 2021-02-01 16:24:47 +00:00
n1474335
339c741a2c 9.22.2 2021-02-01 16:14:38 +00:00
n1474335
d1bde23f00 Merge branch 'n1073645-datetime' 2021-02-01 16:13:57 +00:00
n1474335
be544faf0f Merge branch 'datetime' of https://github.com/n1073645/CyberChef into n1073645-datetime 2021-02-01 16:13:43 +00:00
n1474335
eff77fd3bb 9.22.1 2021-02-01 16:11:46 +00:00
n1474335
3df57ba3dd Added big and little endian options for Windows timestamp conversion ops 2021-02-01 16:11:39 +00:00
n1474335
4bae662357 Merge branch 'n1073645-FiletimeEndianness' 2021-02-01 15:56:03 +00:00
n1474335
357c90546e Merge branch 'FiletimeEndianness' of https://github.com/n1073645/CyberChef into n1073645-FiletimeEndianness 2021-02-01 15:55:42 +00:00
mt3571
887ea0cf06 Changed an incorrect name 2020-12-01 13:49:34 +00:00
mt3571
3e0525ee9e Added in a new file to store the list of JWT algorithms that can be used, as this error was occurring due to a mismatch between what you could sign and what you could verify 2020-12-01 13:38:01 +00:00
n1073645
f6c8c9e76c swap endianness argument added to Windows Filetime To Unix Timestamp 2020-08-24 10:39:18 +01:00
n1073645
3bfddd708c rectify week number 2020-08-17 10:40:00 +01:00
n1073645
2781640a2a JSON to CSV improvements 2020-07-29 15:27:55 +01:00
n1073645
7989f119d3 Linting Modifications 2020-07-16 09:56:30 +01:00
Scott Howard
2e0aa7ae87 Don't pad rail fence decode fixes #1069 2020-07-15 22:05:15 -04:00
n1073645
c01ce90e06 Tests Added 2020-07-06 11:20:54 +01:00
n1073645
d68c8cb845 Casing Variations 2020-07-06 10:44:05 +01:00
Stephen G
de727bcddc use nvmrc file
avoids storing node version in travis to allow nvm tool for local dev
2020-06-13 14:51:37 -07:00
Matt
d4ae241758 Merge branch 'master' into rsa 2020-06-08 15:55:37 +01:00
hettysymes
88947b9d42 Added operation description note and modified comment formatting 2020-06-08 12:27:40 +01:00
hettysymes
3c68ad1302 Modified control rotor stepping so the next control rotor steps once the previous rotor reaches "O" and added tests 2020-06-07 17:45:17 +01:00
hettysymes
e2b3389da6 Added SIGABA simple test 2020-06-06 19:47:15 +01:00
hettysymes
938385c18b Fixed grunt lint errors 2020-06-06 19:46:42 +01:00
hettysymes
5d01b06877 Added copyright and clarified description 2020-06-06 19:46:42 +01:00
hettysymes
f007c093eb Emulation of the WW2 SIGABA machine
I have created an emulation of the SIGABA machine and have tested it against some test data from a Master's thesis by Miao Ai: https://scholarworks.sjsu.edu/cgi/viewcontent.cgi?article=1237&context=etd_projects
2020-06-06 19:46:42 +01:00
n1073645
7526f4d7b1 Generate Epoch Time Operation Added 2020-06-01 13:47:51 +01:00
n1073645
fae96af17d Info for sm3 added 2020-04-24 14:13:55 +01:00
n1073645
57c1a03c4f Option structures added for hashing algorithms 2020-04-24 14:04:13 +01:00
Alexander Prinzhorn
cb8fe42c66 Put Base64 after Base62 2020-04-16 10:20:38 +02:00
Alexander Prinzhorn
7f4b2574b0 Use proper booleans instead of relying on truthy/falsy values 2020-04-16 09:59:43 +02:00
Matt
fad163e0eb Added tests (that can't be run) 2020-04-07 21:16:29 +01:00
Matt
7ad3992bd1 Add RSA Decrypt Operation 2020-04-07 13:31:33 +01:00
Matt
e7b5c0e37c Add RSA Encrypt Operation 2020-04-07 13:31:17 +01:00
n1073645
cc35127459 AAD for AES Added 2020-04-07 13:03:24 +01:00
Matt
1c0ecd29c2 Fix RSA operations 2020-04-07 11:45:54 +01:00
n1073645
1f0fddd0e9 Added magic signature to Microsoft Script Decoder 2020-04-07 10:33:15 +01:00
Matt
18c6b9bc09 Add RSA Verify operation 2020-04-06 15:24:22 +01:00
Matt
2233b9a094 Comment and add error handling to generate and sign 2020-04-06 15:24:06 +01:00
Matt
e0f000b913 Fixed RSA generation and added digest option to verify 2020-04-06 13:35:14 +01:00
Matt
73864e0809 Merge branch 'master' into features/rsa 2020-04-05 12:08:24 +01:00
n1073645
cd8a85975c Info and description 2020-04-02 15:59:58 +01:00
n1073645
2f94ec20b0 Added to categories 2020-04-02 15:58:03 +01:00
n1073645
09d9deae43 ID3 Extractor Added. 2020-04-02 15:43:55 +01:00
71819
209fc07eac Issue 991: Add CBOR Decode operation 2020-03-30 11:31:25 +01:00
71819
ae70cb89ed Issue 991: Add CBOR Encode operation 2020-03-30 11:31:25 +01:00
n1073645
bda36e508a Regexes for magic for the new alphabets 2020-03-27 13:27:56 +00:00
n1073645
d2ea1273da Merge remote-tracking branch 'upstream/master' into base64Alphabets 2020-03-27 13:09:03 +00:00
n1073645
2e0af64ac3 PGP secring signatures and Mac OS X keyring extractor added 2020-03-17 14:53:05 +00:00
n1073645
30bc8dfbe9 UNIX Format Added for ToHexdump 2020-03-13 10:38:37 +00:00
n1073645
53a579028c Added only ASCII flag to ToHexdump 2020-03-12 09:30:48 +00:00
n1073645
3a2580fbc2 Extra Base64 Alphabets 2020-01-22 10:35:11 +00:00
Matt
4d7988b78e Fixed RSA key generation 2019-09-30 13:12:10 +01:00
Matt
841e760b04 Merge remote-tracking branch 'upstream/master' into features/rsa 2019-09-30 11:03:41 +01:00
Matt C
31e758ca45 Attempt to make RSA key generation functional 2018-08-31 11:25:05 +01:00
GCHQ 77703
f81ca3ba60 Implement RSA generation and signing of messages 2018-08-30 22:38:01 +01:00
187 changed files with 29105 additions and 8817 deletions

View File

@@ -1,5 +1,5 @@
{
"parser": "babel-eslint",
"parser": "@babel/eslint-parser",
"parserOptions": {
"ecmaVersion": 9,
"ecmaFeatures": {
@@ -63,7 +63,8 @@
}],
"linebreak-style": ["error", "unix"],
"quotes": ["error", "double", {
"avoidEscape": true
"avoidEscape": true,
"allowTemplateLiterals": true
}],
"camelcase": ["error", {
"properties": "always"

33
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: "CodeQL Analysis"
on:
workflow_dispatch:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '22 17 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -1,6 +1,7 @@
name: Master
name: "Master Build, Test & Deploy"
on:
workflow_dispatch:
push:
branches:
- master
@@ -14,12 +15,12 @@ jobs:
- name: Set node version
uses: actions/setup-node@v1
with:
node-version: '10.x'
node-version: '17.x'
- name: Install
run: |
npm install
export NODE_OPTIONS=--max_old_space_size=2048
npm run setheapsize
- name: Lint
run: npx grunt lint
@@ -27,7 +28,7 @@ jobs:
- name: Unit Tests
run: |
npm test
npx grunt testnodeconsumer
npm run testnodeconsumer
- name: Production Build
if: success()
@@ -36,9 +37,9 @@ jobs:
- name: Generate sitemap
run: npx grunt exec:sitemap
- name: UI Tests
if: success()
run: xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
# - name: UI Tests
# if: success()
# run: xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
- name: Prepare for GitHub Pages
if: success()

38
.github/workflows/pull_requests.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: "Pull Requests"
on:
workflow_dispatch:
pull_request:
types: [synchronize, opened, reopened]
jobs:
main:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set node version
uses: actions/setup-node@v1
with:
node-version: '17.x'
- name: Install
run: |
npm install
npm run setheapsize
- name: Lint
run: npx grunt lint
- name: Unit Tests
run: |
npm test
npm run testnodeconsumer
- name: Production Build
if: success()
run: npx grunt prod
# - name: UI Tests
# if: success()
# run: xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui

View File

@@ -1,6 +1,7 @@
name: Release
name: "Releases"
on:
workflow_dispatch:
push:
tags:
- 'v*'
@@ -14,12 +15,12 @@ jobs:
- name: Set node version
uses: actions/setup-node@v1
with:
node-version: '10.x'
node-version: '17.x'
- name: Install
run: |
npm install
export NODE_OPTIONS=--max_old_space_size=2048
npm run setheapsize
- name: Lint
run: npx grunt lint
@@ -27,15 +28,15 @@ jobs:
- name: Unit Tests
run: |
npm test
npx grunt testnodeconsumer
npm run testnodeconsumer
- name: Production Build
if: success()
run: npx grunt prod
- name: UI Tests
if: success()
run: xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
# - name: UI Tests
# if: success()
# run: xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
- name: Upload Release Assets
if: success()
@@ -47,6 +48,7 @@ jobs:
tag: ${{ github.ref }}
overwrite: true
file_glob: true
body: "See the [CHANGELOG](https://github.com/gchq/CyberChef/blob/master/CHANGELOG.md) and [commit messages](https://github.com/gchq/CyberChef/commits/master) for details."
- name: Publish to NPM
if: success()

2
.gitignore vendored
View File

@@ -11,4 +11,4 @@ src/node/config/OperationConfig.json
src/node/index.mjs
**/*.DS_Store
tests/browser/output/*
.node-version

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
17

View File

@@ -1,7 +1,71 @@
# Changelog
## Versioning
CyberChef uses the [semver](https://semver.org/) system to manage versioning: `<MAJOR>.<MINOR>.<PATCH>`.
- MAJOR version changes represent a significant change to the fundamental architecture of CyberChef and may (but don't always) make breaking changes that are not backwards compatible.
- MINOR version changes usually mean the addition of new operations or reasonably significant new features.
- PATCH versions are used for bug fixes and any other small tweaks that modify or improve existing capabilities.
All major and minor version changes will be documented in this file. Details of patch-level version changes can be found in [commit messages](https://github.com/gchq/CyberChef/commits/master).
## Details
### [9.38.0] - 2022-05-30
- Added 'Parse TCP' operation [@n1474335] | [a895d1d]
### [9.37.0] - 2022-03-29
- 'SM4 Encrypt' and 'SM4 Decrypt' operations added [@swesven] | [#1189]
- NoPadding options added for CBC and ECB modes in AES, DES and Triple DES Decrypt operations [@swesven] | [#1189]
### [9.36.0] - 2022-03-29
- 'SIGABA' operation added [@hettysymes] | [#934]
### [9.35.0] - 2022-03-28
- 'To Base45' and 'From Base45' operations added [@t-8ch] | [#1242]
### [9.34.0] - 2022-03-28
- 'Get All Casings' operation added [@n1073645] | [#1065]
### [9.33.0] - 2022-03-25
- Updated to support Node 17 [@n1474335] [@john19696] [@t-8ch] | [[#1326] [#1313] [#1244]
- Improved CJS and ESM module support [@d98762625] | [#1037]
### [9.32.0] - 2021-08-18
- 'Protobuf Encode' operation added and decode operation modified to allow decoding with full and partial schemas [@n1474335] | [dd18e52]
### [9.31.0] - 2021-08-10
- 'HASSH Client Fingerprint' and 'HASSH Server Fingerprint' operations added [@n1474335] | [e9ca4dc]
### [9.30.0] - 2021-08-10
- 'JA3S Fingerprint' operation added [@n1474335] | [289a417]
### [9.29.0] - 2021-07-28
- 'JA3 Fingerprint' operation added [@n1474335] | [9a33498]
### [9.28.0] - 2021-03-26
- 'CBOR Encode' and 'CBOR Decode' operations added [@Danh4] | [#999]
### [9.27.0] - 2021-02-12
- 'Fuzzy Match' operation added [@n1474335] | [8ad18b]
### [9.26.0] - 2021-02-11
- 'Get Time' operation added [@n1073645] [@n1474335] | [#1045]
### [9.25.0] - 2021-02-11
- 'Extract ID3' operation added [@n1073645] [@n1474335] | [#1006]
### [9.24.0] - 2021-02-02
- 'SM3' hashing function added along with more configuration options for other hashing operations [@n1073645] [@n1474335] | [#1022]
### [9.23.0] - 2021-02-01
- Various RSA operations added to encrypt, decrypt, sign, verify and generate keys [@mattnotmitt] [@GCHQ77703] | [#652]
### [9.22.0] - 2021-02-01
- 'Unicode Text Format' operation added [@mattnotmitt] | [#1083]
### [9.21.0] - 2020-06-12
- Node API now exports `magic` operation [@d98762625] | [#1049]
@@ -74,7 +138,7 @@ All major and minor version changes will be documented in this file. Details of
<details>
<summary>Click to expand v8 minor versions</summary>
### [8.38.0] - 2019-07-03
- 'Streebog' and 'GOST hash' operations added [@MShwed] [@n1474335] | [#530]
@@ -227,6 +291,24 @@ All major and minor version changes will be documented in this file. Details of
[9.38.0]: https://github.com/gchq/CyberChef/releases/tag/v9.38.0
[9.37.0]: https://github.com/gchq/CyberChef/releases/tag/v9.37.0
[9.36.0]: https://github.com/gchq/CyberChef/releases/tag/v9.36.0
[9.35.0]: https://github.com/gchq/CyberChef/releases/tag/v9.35.0
[9.34.0]: https://github.com/gchq/CyberChef/releases/tag/v9.34.0
[9.33.0]: https://github.com/gchq/CyberChef/releases/tag/v9.33.0
[9.32.0]: https://github.com/gchq/CyberChef/releases/tag/v9.32.0
[9.31.0]: https://github.com/gchq/CyberChef/releases/tag/v9.31.0
[9.30.0]: https://github.com/gchq/CyberChef/releases/tag/v9.30.0
[9.29.0]: https://github.com/gchq/CyberChef/releases/tag/v9.29.0
[9.28.0]: https://github.com/gchq/CyberChef/releases/tag/v9.28.0
[9.27.0]: https://github.com/gchq/CyberChef/releases/tag/v9.27.0
[9.26.0]: https://github.com/gchq/CyberChef/releases/tag/v9.26.0
[9.25.0]: https://github.com/gchq/CyberChef/releases/tag/v9.25.0
[9.24.0]: https://github.com/gchq/CyberChef/releases/tag/v9.24.0
[9.23.0]: https://github.com/gchq/CyberChef/releases/tag/v9.23.0
[9.22.0]: https://github.com/gchq/CyberChef/releases/tag/v9.22.0
[9.21.0]: https://github.com/gchq/CyberChef/releases/tag/v9.21.0
[9.20.0]: https://github.com/gchq/CyberChef/releases/tag/v9.20.0
[9.19.0]: https://github.com/gchq/CyberChef/releases/tag/v9.19.0
[9.18.0]: https://github.com/gchq/CyberChef/releases/tag/v9.18.0
@@ -326,6 +408,19 @@ All major and minor version changes will be documented in this file. Details of
[@pointhi]: https://github.com/pointhi
[@MarvinJWendt]: https://github.com/MarvinJWendt
[@dmfj]: https://github.com/dmfj
[@mattnotmitt]: https://github.com/mattnotmitt
[@Danh4]: https://github.com/Danh4
[@john19696]: https://github.com/john19696
[@t-8ch]: https://github.com/t-8ch
[@hettysymes]: https://github.com/hettysymes
[@swesven]: https://github.com/swesven
[8ad18b]: https://github.com/gchq/CyberChef/commit/8ad18bc7db6d9ff184ba3518686293a7685bf7b7
[9a33498]: https://github.com/gchq/CyberChef/commit/9a33498fed26a8df9c9f35f39a78a174bf50a513
[289a417]: https://github.com/gchq/CyberChef/commit/289a417dfb5923de5e1694354ec42a08d9395bfe
[e9ca4dc]: https://github.com/gchq/CyberChef/commit/e9ca4dc9caf98f33fd986431cd400c88082a42b8
[dd18e52]: https://github.com/gchq/CyberChef/commit/dd18e529939078b89867297b181a584e8b2cc7da
[a895d1d]: https://github.com/gchq/CyberChef/commit/a895d1d82a2f92d440a0c5eca2bc7c898107b737
[#95]: https://github.com/gchq/CyberChef/pull/299
[#173]: https://github.com/gchq/CyberChef/pull/173
@@ -387,14 +482,29 @@ All major and minor version changes will be documented in this file. Details of
[#625]: https://github.com/gchq/CyberChef/pull/625
[#627]: https://github.com/gchq/CyberChef/pull/627
[#632]: https://github.com/gchq/CyberChef/pull/632
[#652]: https://github.com/gchq/CyberChef/pull/652
[#653]: https://github.com/gchq/CyberChef/pull/653
[#674]: https://github.com/gchq/CyberChef/pull/674
[#683]: https://github.com/gchq/CyberChef/pull/683
[#865]: https://github.com/gchq/CyberChef/pull/865
[#912]: https://github.com/gchq/CyberChef/pull/912
[#917]: https://github.com/gchq/CyberChef/pull/917
[#934]: https://github.com/gchq/CyberChef/pull/934
[#948]: https://github.com/gchq/CyberChef/pull/948
[#952]: https://github.com/gchq/CyberChef/pull/952
[#965]: https://github.com/gchq/CyberChef/pull/965
[#966]: https://github.com/gchq/CyberChef/pull/966
[#987]: https://github.com/gchq/CyberChef/pull/987
[#999]: https://github.com/gchq/CyberChef/pull/999
[#1006]: https://github.com/gchq/CyberChef/pull/1006
[#1022]: https://github.com/gchq/CyberChef/pull/1022
[#1037]: https://github.com/gchq/CyberChef/pull/1037
[#1045]: https://github.com/gchq/CyberChef/pull/1045
[#1049]: https://github.com/gchq/CyberChef/pull/1049
[#1065]: https://github.com/gchq/CyberChef/pull/1065
[#1083]: https://github.com/gchq/CyberChef/pull/1083
[#1189]: https://github.com/gchq/CyberChef/pull/1189
[#1242]: https://github.com/gchq/CyberChef/pull/1242
[#1244]: https://github.com/gchq/CyberChef/pull/1244
[#1313]: https://github.com/gchq/CyberChef/pull/1313
[#1326]: https://github.com/gchq/CyberChef/pull/1326

View File

@@ -6,6 +6,8 @@ const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPl
const glob = require("glob");
const path = require("path");
const nodeFlags = "--experimental-modules --experimental-json-modules --experimental-specifier-resolution=node --no-warnings --no-deprecation";
/**
* Grunt configuration for building the app in various formats.
*
@@ -48,7 +50,7 @@ module.exports = function (grunt) {
grunt.registerTask("testnodeconsumer",
"A task which checks whether consuming CJS and ESM apps work with the CyberChef build",
["exec:setupNodeConsumers", "exec:testCJSNodeConsumer", "exec:testESMNodeConsumer", "exec:testESMDeepImportNodeConsumer", "exec:teardownNodeConsumers"]);
["exec:setupNodeConsumers", "exec:testCJSNodeConsumer", "exec:testESMNodeConsumer", "exec:teardownNodeConsumers"]);
grunt.registerTask("default",
"Lints the code base",
@@ -78,7 +80,6 @@ module.exports = function (grunt) {
grunt.loadNpmTasks("grunt-contrib-watch");
grunt.loadNpmTasks("grunt-chmod");
grunt.loadNpmTasks("grunt-exec");
grunt.loadNpmTasks("grunt-accessibility");
grunt.loadNpmTasks("grunt-concurrent");
grunt.loadNpmTasks("grunt-contrib-connect");
grunt.loadNpmTasks("grunt-zip");
@@ -175,7 +176,7 @@ module.exports = function (grunt) {
// previous one fails. & would coninue on a fail
.join("&&")
// Windows does not support \n properly
.replace("\n", "\\n");
.replace(/\n/g, "\\n");
}
grunt.initConfig({
@@ -188,76 +189,47 @@ module.exports = function (grunt) {
standalone: ["build/prod/CyberChef*.html"]
},
eslint: {
options: {
configFile: "./.eslintrc.json"
},
configs: ["*.{js,mjs}"],
core: ["src/core/**/*.{js,mjs}", "!src/core/vendor/**/*", "!src/core/operations/legacy/**/*"],
web: ["src/web/**/*.{js,mjs}", "!src/web/static/**/*"],
node: ["src/node/**/*.{js,mjs}"],
tests: ["tests/**/*.{js,mjs}"],
},
accessibility: {
options: {
accessibilityLevel: "WCAG2A",
verbose: false,
ignore: [
"WCAG2A.Principle1.Guideline1_3.1_3_1.H42.2"
]
},
test: {
src: ["build/**/*.html"]
}
},
webpack: {
options: webpackConfig,
web: webpackProdConf(),
},
"webpack-dev-server": {
options: {
webpack: webpackConfig,
host: "0.0.0.0",
port: grunt.option("port") || 8080,
disableHostCheck: true,
overlay: true,
inline: false,
clientLogLevel: "error",
stats: {
children: false,
chunks: false,
modules: false,
entrypoints: false,
warningsFilter: [
/source-map/,
/dependency is an expression/,
/export 'default'/,
/Can't resolve 'sodium'/
],
}
},
options: webpackConfig,
start: {
webpack: {
mode: "development",
target: "web",
entry: Object.assign({
main: "./src/web/index.js"
}, moduleEntryPoints),
resolve: {
alias: {
"./config/modules/OpModules.mjs": "./config/modules/Default.mjs"
}
mode: "development",
target: "web",
entry: Object.assign({
main: "./src/web/index.js"
}, moduleEntryPoints),
resolve: {
alias: {
"./config/modules/OpModules.mjs": "./config/modules/Default.mjs"
}
},
devServer: {
port: grunt.option("port") || 8080,
client: {
logging: "error",
overlay: true
},
plugins: [
new webpack.DefinePlugin(BUILD_CONSTANTS),
new HtmlWebpackPlugin({
filename: "index.html",
template: "./src/web/html/index.html",
chunks: ["main"],
compileTime: compileTime,
version: pkg.version,
})
]
}
hot: "only"
},
plugins: [
new webpack.DefinePlugin(BUILD_CONSTANTS),
new HtmlWebpackPlugin({
filename: "index.html",
template: "./src/web/html/index.html",
chunks: ["main"],
compileTime: compileTime,
version: pkg.version,
})
]
}
},
zip: {
@@ -362,15 +334,15 @@ module.exports = function (grunt) {
command: "git gc --prune=now --aggressive"
},
sitemap: {
command: "node --experimental-modules --no-warnings --no-deprecation src/web/static/sitemap.mjs > build/prod/sitemap.xml",
command: `node ${nodeFlags} src/web/static/sitemap.mjs > build/prod/sitemap.xml`,
sync: true
},
generateConfig: {
command: chainCommands([
"echo '\n--- Regenerating config files. ---'",
"echo [] > src/core/config/OperationConfig.json",
"node --experimental-modules --no-warnings --no-deprecation src/core/config/scripts/generateOpsIndex.mjs",
"node --experimental-modules --no-warnings --no-deprecation src/core/config/scripts/generateConfig.mjs",
`node ${nodeFlags} src/core/config/scripts/generateOpsIndex.mjs`,
`node ${nodeFlags} src/core/config/scripts/generateConfig.mjs`,
"echo '--- Config scripts finished. ---\n'"
]),
sync: true
@@ -378,7 +350,7 @@ module.exports = function (grunt) {
generateNodeIndex: {
command: chainCommands([
"echo '\n--- Regenerating node index ---'",
"node --experimental-modules --no-warnings --no-deprecation src/node/config/scripts/generateNodeIndex.mjs",
`node ${nodeFlags} src/node/config/scripts/generateNodeIndex.mjs`,
"echo '--- Node index generated. ---\n'"
]),
sync: true
@@ -406,24 +378,27 @@ module.exports = function (grunt) {
testCJSNodeConsumer: {
command: chainCommands([
`cd ${nodeConsumerTestPath}`,
"node --no-warnings cjs-consumer.js",
`node ${nodeFlags} cjs-consumer.js`,
]),
stdout: false,
},
testESMNodeConsumer: {
command: chainCommands([
`cd ${nodeConsumerTestPath}`,
"node --no-warnings --experimental-modules esm-consumer.mjs",
]),
stdout: false,
},
testESMDeepImportNodeConsumer: {
command: chainCommands([
`cd ${nodeConsumerTestPath}`,
"node --no-warnings --experimental-modules esm-deep-import-consumer.mjs",
`node ${nodeFlags} esm-consumer.mjs`,
]),
stdout: false,
},
fixCryptoApiImports: {
command: [
`[[ "$OSTYPE" == "darwin"* ]]`,
"&&",
`find ./node_modules/crypto-api/src/ \\( -type d -name .git -prune \\) -o -type f -print0 | xargs -0 sed -i '' -e '/\\.mjs/!s/\\(from "\\.[^"]*\\)";/\\1.mjs";/g'`,
"||",
`find ./node_modules/crypto-api/src/ \\( -type d -name .git -prune \\) -o -type f -print0 | xargs -0 sed -i -e '/\\.mjs/!s/\\(from "\\.[^"]*\\)";/\\1.mjs";/g'`
].join(" "),
stdout: false
}
},
});
};

View File

@@ -1,7 +1,7 @@
# CyberChef
[![Build Status](https://travis-ci.org/gchq/CyberChef.svg?branch=master)](https://travis-ci.org/gchq/CyberChef)
[![dependencies Status](https://david-dm.org/gchq/CyberChef/status.svg)](https://david-dm.org/gchq/CyberChef)
[![](https://github.com/gchq/CyberChef/workflows/Master%20Build,%20Test%20&%20Deploy/badge.svg)](https://github.com/gchq/CyberChef/actions?query=workflow%3A%22Master+Build%2C+Test+%26+Deploy%22)
[![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/gchq/CyberChef.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/gchq/CyberChef/context:javascript)
[![npm](https://img.shields.io/npm/v/cyberchef.svg)](https://www.npmjs.com/package/cyberchef)
[![](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/gchq/CyberChef/blob/master/LICENSE)
[![Gitter](https://badges.gitter.im/gchq/CyberChef.svg)](https://gitter.im/gchq/CyberChef?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
@@ -9,7 +9,7 @@
#### *The Cyber Swiss Army Knife*
CyberChef is a simple, intuitive web app for carrying out all manner of "cyber" operations within a web browser. These operations include simple encoding like XOR or Base64, more complex encryption like AES, DES and Blowfish, creating binary and hexdumps, compression and decompression of data, calculating hashes and checksums, IPv6 and X.509 parsing, changing character encodings, and much more.
CyberChef is a simple, intuitive web app for carrying out all manner of "cyber" operations within a web browser. These operations include simple encoding like XOR and Base64, more complex encryption like AES, DES and Blowfish, creating binary and hexdumps, compression and decompression of data, calculating hashes and checksums, IPv6 and X.509 parsing, changing character encodings, and much more.
The tool is designed to enable both technical and non-technical analysts to manipulate data in complex ways without having to deal with complex tools or algorithms. It was conceived, designed, built and incrementally improved by an analyst in their 10% innovation time over several years.
@@ -105,7 +105,7 @@ An installation walkthrough, how-to guides for adding new operations and themes,
## Licencing
CyberChef is released under the [Apache 2.0 Licence](https://www.apache.org/licenses/LICENSE-2.0) and is covered by [Crown Copyright](https://www.nationalarchives.gov.uk/information-management/re-using-public-sector-information/copyright-and-re-use/crown-copyright/).
CyberChef is released under the [Apache 2.0 Licence](https://www.apache.org/licenses/LICENSE-2.0) and is covered by [Crown Copyright](https://www.nationalarchives.gov.uk/information-management/re-using-public-sector-information/uk-government-licensing-framework/crown-copyright/).
[1]: https://gchq.github.io/CyberChef

View File

@@ -11,6 +11,7 @@ module.exports = function(api) {
],
"plugins": [
"dynamic-import-node",
"@babel/plugin-syntax-import-assertions",
[
"babel-plugin-transform-builtin-extend", {
"globals": ["Error"]

View File

@@ -10,11 +10,12 @@
"start_process": true,
"server_path": "./node_modules/.bin/chromedriver",
"port": 9515,
"log_path": false
"log_path": "tests/browser/output"
},
"desiredCapabilities": {
"browserName": "chrome"
}
},
"enable_fail_fast": true
},
"dev": {

28344
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "cyberchef",
"version": "9.22.0",
"version": "9.38.5",
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
"author": "n1474335 <n1474335@gmail.com>",
"homepage": "https://gchq.github.io/CyberChef",
@@ -27,152 +27,156 @@
"type": "git",
"url": "https://github.com/gchq/CyberChef/"
},
"main": "src/node/cjs.js",
"module": "src/node/index.mjs",
"main": "src/node/wrapper.js",
"exports": {
"import": "./src/node/index.mjs",
"require": "./src/node/wrapper.js"
},
"bugs": "https://github.com/gchq/CyberChef/issues",
"browserslist": [
"Chrome >= 50",
"Firefox >= 38",
"node >= 10"
"node >= 16"
],
"devDependencies": {
"@babel/core": "^7.12.10",
"@babel/plugin-transform-runtime": "^7.12.10",
"@babel/preset-env": "^7.12.10",
"autoprefixer": "^10.1.0",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.2.2",
"@babel/core": "^7.18.2",
"@babel/eslint-parser": "^7.18.2",
"@babel/plugin-syntax-import-assertions": "^7.17.12",
"@babel/plugin-transform-runtime": "^7.18.2",
"@babel/preset-env": "^7.18.2",
"@babel/runtime": "^7.18.3",
"autoprefixer": "^10.4.7",
"babel-loader": "^8.2.5",
"babel-plugin-dynamic-import-node": "^2.3.3",
"chromedriver": "^87.0.4",
"cli-progress": "^3.8.2",
"babel-plugin-transform-builtin-extend": "1.1.2",
"chromedriver": "^101.0.0",
"cli-progress": "^3.11.1",
"colors": "^1.4.0",
"copy-webpack-plugin": "^7.0.0",
"css-loader": "^5.0.1",
"eslint": "^7.15.0",
"exports-loader": "^1.1.1",
"file-loader": "^6.2.0",
"grunt": "^1.3.0",
"grunt-accessibility": "~6.0.0",
"copy-webpack-plugin": "^11.0.0",
"core-js": "^3.22.8",
"css-loader": "6.7.1",
"eslint": "^8.16.0",
"grunt": "^1.5.3",
"grunt-chmod": "~1.1.1",
"grunt-concurrent": "^3.0.0",
"grunt-contrib-clean": "~2.0.0",
"grunt-contrib-clean": "~2.0.1",
"grunt-contrib-connect": "^3.0.0",
"grunt-contrib-copy": "~1.0.0",
"grunt-contrib-watch": "^1.1.0",
"grunt-eslint": "^23.0.0",
"grunt-eslint": "^24.0.0",
"grunt-exec": "~3.0.0",
"grunt-webpack": "^4.0.2",
"grunt-webpack": "^5.0.0",
"grunt-zip": "^0.18.2",
"html-webpack-plugin": "^4.5.0",
"imports-loader": "^1.2.0",
"mini-css-extract-plugin": "^1.3.3",
"nightwatch": "^1.5.1",
"node-sass": "^5.0.0",
"postcss": "^8.2.1",
"postcss-css-variables": "^0.17.0",
"postcss-import": "^13.0.0",
"postcss-loader": "^4.1.0",
"prompt": "^1.0.0",
"sass-loader": "^10.1.0",
"sitemap": "^6.3.3",
"style-loader": "^2.0.0",
"svg-url-loader": "^7.1.1",
"url-loader": "^4.1.1",
"webpack": "^5.10.1",
"webpack-bundle-analyzer": "^4.2.0",
"webpack-dev-server": "^3.11.0",
"webpack-node-externals": "^2.5.2",
"worker-loader": "^3.0.6"
"html-webpack-plugin": "^5.5.0",
"imports-loader": "^4.0.0",
"mini-css-extract-plugin": "2.6.0",
"modify-source-webpack-plugin": "^3.0.0",
"nightwatch": "^2.1.7",
"postcss": "^8.4.14",
"postcss-css-variables": "^0.18.0",
"postcss-import": "^14.1.0",
"postcss-loader": "^7.0.0",
"prompt": "^1.3.0",
"sass-loader": "^13.0.0",
"sitemap": "^7.1.1",
"terser": "^5.14.0",
"webpack": "^5.73.0",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-dev-server": "4.9.1",
"webpack-node-externals": "^3.0.0",
"worker-loader": "^3.0.8"
},
"dependencies": {
"@babel/polyfill": "^7.12.1",
"@babel/runtime": "^7.12.5",
"arrive": "^2.4.1",
"avsc": "^5.5.3",
"babel-plugin-transform-builtin-extend": "1.1.2",
"avsc": "^5.7.4",
"bcryptjs": "^2.4.3",
"bignumber.js": "^9.0.1",
"blakejs": "^1.1.0",
"bootstrap": "4.5.3",
"bootstrap-colorpicker": "^3.2.0",
"bignumber.js": "^9.0.2",
"blakejs": "^1.2.1",
"bootstrap": "4.6.1",
"bootstrap-colorpicker": "^3.4.0",
"bootstrap-material-design": "^4.1.3",
"browserify-zlib": "^0.2.0",
"bson": "^4.2.2",
"bson": "^4.6.4",
"buffer": "^6.0.3",
"cbor": "8.1.0",
"chi-squared": "^1.1.0",
"codepage": "^1.14.0",
"core-js": "^3.8.1",
"codepage": "^1.15.0",
"crypto-api": "^0.8.5",
"crypto-browserify": "^3.12.0",
"crypto-js": "^4.0.0",
"crypto-js": "^4.1.1",
"ctph.js": "0.0.5",
"d3": "^6.3.1",
"d3": "7.4.4",
"d3-hexbin": "^0.2.2",
"diff": "^5.0.0",
"es6-promisify": "^6.1.1",
"diff": "^5.1.0",
"es6-promisify": "^7.0.0",
"escodegen": "^2.0.0",
"esm": "^3.2.25",
"esprima": "^4.0.1",
"exif-parser": "^0.1.12",
"file-saver": "^2.0.5",
"geodesy": "^1.1.3",
"highlight.js": "^10.4.1",
"flat": "^5.0.2",
"geodesy": "1.1.3",
"highlight.js": "^11.5.1",
"jimp": "^0.16.1",
"jquery": "3.5.1",
"jquery": "3.6.0",
"js-crc": "^0.2.0",
"js-sha3": "^0.8.0",
"jsesc": "^3.0.2",
"jsonpath": "^1.0.2",
"jsonpath": "^1.1.1",
"jsonwebtoken": "^8.5.1",
"jsqr": "^1.3.1",
"jsrsasign": "10.1.4",
"jsqr": "^1.4.0",
"jsrsasign": "^10.5.23",
"kbpgp": "2.1.15",
"libbzip2-wasm": "0.0.4",
"libyara-wasm": "^1.1.0",
"lodash": "^4.17.20",
"loglevel": "^1.7.1",
"lodash": "^4.17.21",
"loglevel": "^1.8.0",
"loglevel-message-prefix": "^3.0.0",
"markdown-it": "^12.0.3",
"moment": "^2.29.1",
"moment-timezone": "^0.5.32",
"markdown-it": "^13.0.1",
"moment": "^2.29.3",
"moment-timezone": "^0.5.34",
"ngeohash": "^0.6.3",
"node-forge": "^0.10.0",
"node-forge": "^1.3.1",
"node-md6": "^0.1.0",
"node-sass": "^7.0.1",
"nodom": "^2.4.0",
"notepack.io": "^2.3.0",
"notepack.io": "^3.0.1",
"nwmatcher": "^1.4.4",
"otp": "^0.1.3",
"otp": "0.1.3",
"path": "^0.12.7",
"popper.js": "^1.16.1",
"process": "^0.11.10",
"protobufjs": "^6.11.3",
"qr-image": "^3.2.0",
"scryptsy": "^2.1.0",
"snackbarjs": "^1.1.0",
"sortablejs": "^1.12.0",
"split.js": "^1.6.2",
"ssdeep.js": "0.0.2",
"sortablejs": "^1.15.0",
"split.js": "^1.6.5",
"ssdeep.js": "0.0.3",
"stream-browserify": "^3.0.0",
"terser": "^5.5.1",
"tesseract.js": "^2.1.1",
"ua-parser-js": "^0.7.23",
"tesseract.js": "2.1.5",
"ua-parser-js": "^1.0.2",
"unorm": "^1.6.0",
"utf8": "^3.0.0",
"vkbeautify": "^0.99.3",
"xmldom": "^0.4.0",
"xmldom": "^0.6.0",
"xpath": "0.0.32",
"xregexp": "^4.4.1",
"xregexp": "^5.1.0",
"zlibjs": "^0.3.1"
},
"scripts": {
"start": "grunt dev",
"build": "grunt prod",
"repl": "node src/node/repl.js",
"test": "grunt configTests && node --experimental-modules --no-warnings --no-deprecation tests/node/index.mjs && node --experimental-modules --no-warnings --no-deprecation tests/operations/index.mjs",
"test-node-consumer": "grunt testnodeconsumer",
"testui": "grunt testui",
"start": "npx grunt dev",
"build": "npx grunt prod",
"repl": "node --experimental-modules --experimental-json-modules --experimental-specifier-resolution=node --no-warnings src/node/repl.mjs",
"test": "npx grunt configTests && node --experimental-modules --experimental-json-modules --no-warnings --no-deprecation tests/node/index.mjs && node --experimental-modules --experimental-json-modules --no-warnings --no-deprecation tests/operations/index.mjs",
"testnodeconsumer": "npx grunt testnodeconsumer",
"testui": "npx grunt testui",
"testuidev": "npx nightwatch --env=dev",
"lint": "grunt lint",
"postinstall": "bash postinstall.sh",
"newop": "node --experimental-modules src/core/config/scripts/newOperation.mjs"
"lint": "npx grunt lint",
"postinstall": "npx grunt exec:fixCryptoApiImports",
"newop": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newOperation.mjs",
"minor": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newMinorVersion.mjs",
"getheapsize": "node -e 'console.log(`node heap limit = ${require(\"v8\").getHeapStatistics().heap_size_limit / (1024 * 1024)} Mb`)'",
"setheapsize": "export NODE_OPTIONS=--max_old_space_size=2048"
}
}

View File

@@ -1,8 +0,0 @@
#!/bin/bash
# Add file extensions to Crypto-Api imports
if [[ "$OSTYPE" == "darwin"* ]]; then
find ./node_modules/crypto-api/src/ \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i '' -e '/\.mjs/!s/\(from "\.[^"]*\)";/\1.mjs";/g'
else
find ./node_modules/crypto-api/src/ \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i -e '/\.mjs/!s/\(from "\.[^"]*\)";/\1.mjs";/g'
fi

View File

@@ -7,7 +7,7 @@
*/
import Chef from "./Chef.mjs";
import OperationConfig from "./config/OperationConfig.json";
import OperationConfig from "./config/OperationConfig.json" assert {type: "json"};
import OpModules from "./config/modules/OpModules.mjs";
// Add ">" to the start of all log messages in the Chef Worker
@@ -212,7 +212,7 @@ self.loadRequiredModules = function(recipeConfig) {
if (!(module in OpModules)) {
log.info(`Loading ${module} module`);
self.sendStatusMessage(`Loading ${module} module`);
self.importScripts(`${self.docURL}/modules/${module}.js`);
self.importScripts(`${self.docURL}/modules/${module}.js`); // lgtm [js/client-side-unvalidated-url-redirection]
self.sendStatusMessage("");
}
});

View File

@@ -207,7 +207,7 @@ class Dish {
const data = new Uint8Array(this.value.slice(0, 2048)),
types = detectFileType(data);
if (!types.length || !types[0].mime || !types[0].mime === "text/plain") {
if (!types.length || !types[0].mime || !(types[0].mime === "text/plain")) {
return null;
} else {
return types[0].mime;

View File

@@ -4,7 +4,7 @@
* @license Apache-2.0
*/
import OperationConfig from "./config/OperationConfig.json";
import OperationConfig from "./config/OperationConfig.json" assert {type: "json"};
import OperationError from "./errors/OperationError.mjs";
import Operation from "./Operation.mjs";
import DishError from "./errors/DishError.mjs";
@@ -46,7 +46,7 @@ class Recipe {
module: OperationConfig[c.op].module,
ingValues: c.args,
breakpoint: c.breakpoint,
disabled: c.disabled,
disabled: c.disabled || c.op === "Comment",
});
});
}

View File

@@ -170,13 +170,18 @@ class Utils {
*
* @param {string} str - The input string to display.
* @param {boolean} [preserveWs=false] - Whether or not to print whitespace.
* @param {boolean} [onlyAscii=false] - Whether or not to replace non ASCII characters.
* @returns {string}
*/
static printable(str, preserveWs=false) {
static printable(str, preserveWs=false, onlyAscii=false) {
if (isWebEnvironment() && window.app && !window.app.options.treatAsUtf8) {
str = Utils.byteArrayToChars(Utils.strToByteArray(str));
}
if (onlyAscii) {
return str.replace(/[^\x20-\x7f]/g, ".");
}
// eslint-disable-next-line no-misleading-character-class
const re = /[\0-\x08\x0B-\x0C\x0E-\x1F\x7F-\x9F\xAD\u0378\u0379\u037F-\u0383\u038B\u038D\u03A2\u0528-\u0530\u0557\u0558\u0560\u0588\u058B-\u058E\u0590\u05C8-\u05CF\u05EB-\u05EF\u05F5-\u0605\u061C\u061D\u06DD\u070E\u070F\u074B\u074C\u07B2-\u07BF\u07FB-\u07FF\u082E\u082F\u083F\u085C\u085D\u085F-\u089F\u08A1\u08AD-\u08E3\u08FF\u0978\u0980\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FC-\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B55\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0C00\u0C04\u0C0D\u0C11\u0C29\u0C34\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5A-\u0C5F\u0C64\u0C65\u0C70-\u0C77\u0C80\u0C81\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0D01\u0D04\u0D0D\u0D11\u0D3B\u0D3C\u0D45\u0D49\u0D4F-\u0D56\u0D58-\u0D5F\u0D64\u0D65\u0D76-\u0D78\u0D80\u0D81\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E86\u0E89\u0E8B\u0E8C\u0E8E-\u0E93\u0E98\u0EA0\u0EA4\u0EA6\u0EA8\u0EA9\u0EAC\u0EBA\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F5-\u13FF\u169D-\u169F\u16F1-\u16FF\u170D\u1715-\u171F\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u180F\u181A-\u181F\u1878-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191D-\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE-\u1AFF\u1B4C-\u1B4F\u1B7D-\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C80-\u1CBF\u1CC8-\u1CCF\u1CF7-\u1CFF\u1DE7-\u1DFB\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u200B-\u200F\u202A-\u202E\u2060-\u206F\u2072\u2073\u208F\u209D-\u209F\u20BB-\u20CF\u20F1-\u20FF\u218A-\u218F\u23F4-\u23FF\u2427-\u243F\u244B-\u245F\u2700\u2B4D-\u2B4F\u2B5A-\u2BFF\u2C2F\u2C5F\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E3C-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u312E-\u3130\u318F\u31BB-\u31BF\u31E4-\u31EF\u321F\u32FF\u4DB6-\u4DBF\u9FCD-\u9FFF\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA698-\uA69E\uA6F8-\uA6FF\uA78F\uA794-\uA79F\uA7AB-\uA7F7\uA82C-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C5-\uA8CD\uA8DA-\uA8DF\uA8FC-\uA8FF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9E0-\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAA7C-\uAA7F\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F-\uABBF\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uD7FF\uE000-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC2-\uFBD2\uFD40-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFE\uFDFF\uFE1A-\uFE1F\uFE27-\uFE2F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD-\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFFB\uFFFE\uFFFF]/g;
const wsRe = /[\x09-\x10\x0D\u2028\u2029]/g;
@@ -704,8 +709,22 @@ class Utils {
* Utils.stripHtmlTags("<div>Test</div>");
*/
static stripHtmlTags(htmlStr, removeScriptAndStyle=false) {
/**
* Recursively remove a pattern from a string until there are no more matches.
* Avoids incomplete sanitization e.g. "aabcbc".replace(/abc/g, "") === "abc"
*
* @param {RegExp} pattern
* @param {string} str
* @returns {string}
*/
function recursiveRemove(pattern, str) {
const newStr = str.replace(pattern, "");
return newStr.length === str.length ? newStr : recursiveRemove(pattern, newStr);
}
if (removeScriptAndStyle) {
htmlStr = htmlStr.replace(/<(script|style)[^>]*>.*<\/(script|style)>/gmi, "");
htmlStr = recursiveRemove(/<script[^>]*>.*?<\/script[^>]*>/gi, htmlStr);
htmlStr = recursiveRemove(/<style[^>]*>.*?<\/style[^>]*>/gi, htmlStr);
}
return htmlStr.replace(/<[^>]+>/g, "");
}
@@ -729,11 +748,10 @@ class Utils {
">": "&gt;",
'"': "&quot;",
"'": "&#x27;", // &apos; not recommended because it's not in the HTML spec
"/": "&#x2F;", // forward slash is included as it helps end an HTML entity
"`": "&#x60;"
};
return str.replace(/[&<>"'/`]/g, function (match) {
return str.replace(/[&<>"'`]/g, function (match) {
return HTML_CHARS[match];
});
}
@@ -878,7 +896,7 @@ class Utils {
while ((m = recipeRegex.exec(recipe))) {
// Translate strings in args back to double-quotes
args = m[2]
args = m[2] // lgtm [js/incomplete-sanitization]
.replace(/"/g, '\\"') // Escape double quotes
.replace(/(^|,|{|:)'/g, '$1"') // Replace opening ' with "
.replace(/([^\\]|(?:\\\\)+)'(,|:|}|$)/g, '$1"$2') // Replace closing ' with "
@@ -1189,6 +1207,30 @@ class Utils {
}[token];
}
/**
* Iterate object in chunks of given size.
*
* @param {Iterable} iterable
* @param {number} chunksize
*/
static* chunked(iterable, chunksize) {
const iterator = iterable[Symbol.iterator]();
while (true) {
const res = [];
for (let i = 0; i < chunksize; i++) {
const next = iterator.next();
if (next.done) {
break;
}
res.push(next.value);
}
if (res.length) {
yield res;
} else {
return;
}
}
}
}
/**

33
src/core/config/Categories.json Executable file → Normal file
View File

@@ -18,15 +18,17 @@
"From Binary",
"To Octal",
"From Octal",
"To Base64",
"From Base64",
"Show Base64 offsets",
"To Base32",
"From Base32",
"To Base45",
"From Base45",
"To Base58",
"From Base58",
"To Base62",
"From Base62",
"To Base64",
"From Base64",
"Show Base64 offsets",
"To Base85",
"From Base85",
"To Base",
@@ -61,7 +63,9 @@
"Parse TLV",
"CSV to JSON",
"JSON to CSV",
"Avro to JSON"
"Avro to JSON",
"CBOR Encode",
"CBOR Decode"
]
},
{
@@ -79,6 +83,8 @@
"RC2 Decrypt",
"RC4",
"RC4 Drop",
"SM4 Encrypt",
"SM4 Decrypt",
"ROT13",
"ROT47",
"XOR",
@@ -116,7 +122,8 @@
"Multiple Bombe",
"Typex",
"Lorenz",
"Colossus"
"Colossus",
"SIGABA"
]
},
{
@@ -134,6 +141,11 @@
"PGP Verify",
"PGP Encrypt and Sign",
"PGP Decrypt and Verify",
"Generate RSA Key Pair",
"RSA Sign",
"RSA Verify",
"RSA Encrypt",
"RSA Decrypt",
"Parse SSH Host Key"
]
},
@@ -178,14 +190,20 @@
"Parse IP range",
"Parse IPv6 address",
"Parse IPv4 header",
"Parse TCP",
"Parse UDP",
"Parse SSH Host Key",
"Parse URI",
"URL Encode",
"URL Decode",
"Protobuf Decode",
"Protobuf Encode",
"VarInt Encode",
"VarInt Decode",
"JA3 Fingerprint",
"JA3S Fingerprint",
"HASSH Client Fingerprint",
"HASSH Server Fingerprint",
"Format MAC addresses",
"Change IP format",
"Group IP addresses",
@@ -218,6 +236,7 @@
"From Case Insensitive Regex",
"Add line numbers",
"Remove line numbers",
"Get All Casings",
"To Table",
"Reverse",
"Sort",
@@ -233,6 +252,7 @@
"Pad lines",
"Find / Replace",
"Regular expression",
"Fuzzy Match",
"Offset checker",
"Hamming Distance",
"Convert distance",
@@ -262,6 +282,7 @@
"Windows Filetime to UNIX Timestamp",
"UNIX Timestamp to Windows Filetime",
"Extract dates",
"Get Time",
"Sleep"
]
},
@@ -281,6 +302,7 @@
"JPath expression",
"CSS selector",
"Extract EXIF",
"Extract ID3",
"Extract Files"
]
},
@@ -314,6 +336,7 @@
"SHA1",
"SHA2",
"SHA3",
"SM3",
"Keccak",
"Shake",
"RIPEMD",

View File

@@ -0,0 +1,144 @@
/**
* This script updates the CHANGELOG when a new minor version is created.
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2022
* @license Apache-2.0
*/
/* eslint no-console: ["off"] */
import prompt from "prompt";
import colors from "colors";
import path from "path";
import fs from "fs";
import process from "process";
const dir = path.join(process.cwd() + "/src/core/config/");
if (!fs.existsSync(dir)) {
console.log("\nCWD: " + process.cwd());
console.log("Error: newMinorVersion.mjs should be run from the project root");
console.log("Example> node --experimental-modules src/core/config/scripts/newMinorVersion.mjs");
process.exit(1);
}
let changelogData = fs.readFileSync(path.join(process.cwd(), "CHANGELOG.md"), "utf8");
const lastVersion = changelogData.match(/## Details\s+### \[(\d+)\.(\d+)\.(\d+)\]/);
const newVersion = [
parseInt(lastVersion[1], 10),
parseInt(lastVersion[2], 10) + 1,
0
];
let knownContributors = changelogData.match(/^\[@([^\]]+)\]/gm);
knownContributors = knownContributors.map(c => c.slice(2, -1));
const date = (new Date()).toISOString().split("T")[0];
const schema = {
properties: {
message: {
description: "A short but descriptive summary of a feature in this version",
example: "Added 'Op name' operation",
prompt: "Feature description",
type: "string",
required: true,
},
author: {
description: "The author of the feature (only one supported, edit manually to add more)",
example: "n1474335",
prompt: "Author",
type: "string",
default: "n1474335"
},
id: {
description: "The PR number or full commit hash for this feature.",
example: "1200",
prompt: "Pull request or commit ID",
type: "string"
},
another: {
description: "y/n",
example: "y",
prompt: "Add another feature?",
type: "string",
pattern: /^[yn]$/,
}
}
};
// Build schema
for (const prop in schema.properties) {
const p = schema.properties[prop];
p.description = "\n" + colors.white(p.description) + colors.cyan("\nExample: " + p.example) + "\n" + colors.green(p.prompt);
}
prompt.message = "";
prompt.delimiter = ":".green;
const features = [];
const authors = [];
const prIDs = [];
const commitIDs = [];
prompt.start();
const getFeature = function() {
prompt.get(schema, (err, result) => {
if (err) {
console.log("\nExiting script.");
process.exit(0);
}
features.push(result);
if (result.another === "y") {
getFeature();
} else {
let message = `### [${newVersion[0]}.${newVersion[1]}.${newVersion[2]}] - ${date}\n`;
features.forEach(feature => {
const id = feature.id.length > 10 ? feature.id.slice(0, 7) : "#" + feature.id;
message += `- ${feature.message} [@${feature.author}] | [${id}]\n`;
if (!knownContributors.includes(feature.author)) {
authors.push(`[@${feature.author}]: https://github.com/${feature.author}`);
}
if (feature.id.length > 10) {
commitIDs.push(`[${id}]: https://github.com/gchq/CyberChef/commit/${feature.id}`);
} else {
prIDs.push(`[#${feature.id}]: https://github.com/gchq/CyberChef/pull/${feature.id}`);
}
});
// Message
changelogData = changelogData.replace(/## Details\n\n/, "## Details\n\n" + message + "\n");
// Tag
const newTag = `[${newVersion[0]}.${newVersion[1]}.${newVersion[2]}]: https://github.com/gchq/CyberChef/releases/tag/v${newVersion[0]}.${newVersion[1]}.${newVersion[2]}\n`;
changelogData = changelogData.replace(/\n\n(\[\d+\.\d+\.\d+\]: https)/, "\n\n" + newTag + "$1");
// Author
authors.forEach(author => {
changelogData = changelogData.replace(/(\n\[@[^\]]+\]: https:\/\/github\.com\/[^\n]+\n)\n/, "$1" + author + "\n\n");
});
// Commit IDs
commitIDs.forEach(commitID => {
changelogData = changelogData.replace(/(\n\[[^\].]+\]: https:\/\/github.com\/gchq\/CyberChef\/commit\/[^\n]+\n)\n/, "$1" + commitID + "\n\n");
});
// PR IDs
prIDs.forEach(prID => {
changelogData = changelogData.replace(/(\n\[#[^\]]+\]: https:\/\/github.com\/gchq\/CyberChef\/pull\/[^\n]+\n)\n/, "$1" + prID + "\n\n");
});
fs.writeFileSync(path.join(process.cwd(), "CHANGELOG.md"), changelogData);
console.log("Written CHANGELOG.md");
}
});
};
getFeature();

View File

@@ -121,7 +121,7 @@ prompt.get(schema, (err, result) => {
const moduleName = result.opName.replace(/\w\S*/g, txt => {
return txt.charAt(0).toUpperCase() + txt.substr(1);
}).replace(/[\s-()/./]/g, "");
}).replace(/[\s-()./]/g, "");
const template = `/**

View File

@@ -1,6 +1,6 @@
import OperationError from "./OperationError.mjs";
import DishError from "./DishError.mjs";
import ExcludedOperationError from "./ExcludedOperationError";
import ExcludedOperationError from "./ExcludedOperationError.mjs";
export {
OperationError,

27
src/core/lib/Base45.mjs Normal file
View File

@@ -0,0 +1,27 @@
/**
* Base45 resources.
*
* @author Thomas Weißschuh [thomas@t-8ch.de]
* @copyright Crown Copyright 2021
* @license Apache-2.0
*/
/**
* Highlight to Base45
*/
export function highlightToBase45(pos, args) {
pos[0].start = Math.floor(pos[0].start / 2) * 3;
pos[0].end = Math.ceil(pos[0].end / 2) * 3;
return pos;
}
/**
* Highlight from Base45
*/
export function highlightFromBase45(pos, args) {
pos[0].start = Math.floor(pos[0].start / 3) * 2;
pos[0].end = Math.ceil(pos[0].end / 3) * 2;
return pos;
}
export const ALPHABET = "0-9A-Z $%*+\\-./:";

View File

@@ -82,15 +82,46 @@ export function toBase64(data, alphabet="A-Za-z0-9+/=") {
* // returns [72, 101, 108, 108, 111]
* fromBase64("SGVsbG8=", null, "byteArray");
*/
export function fromBase64(data, alphabet="A-Za-z0-9+/=", returnType="string", removeNonAlphChars=true) {
export function fromBase64(data, alphabet="A-Za-z0-9+/=", returnType="string", removeNonAlphChars=true, strictMode=false) {
if (!data) {
return returnType === "string" ? "" : [];
}
alphabet = alphabet || "A-Za-z0-9+/=";
alphabet = Utils.expandAlphRange(alphabet).join("");
// Confirm alphabet is a valid length
if (alphabet.length !== 64 && alphabet.length !== 65) { // Allow for padding
throw new OperationError(`Invalid Base64 alphabet length (${alphabet.length}): ${alphabet}`);
throw new OperationError(`Error: Base64 alphabet should be 64 characters long, or 65 with a padding character. Found ${alphabet.length}: ${alphabet}`);
}
// Remove non-alphabet characters
if (removeNonAlphChars) {
const re = new RegExp("[^" + alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g");
data = data.replace(re, "");
}
if (strictMode) {
// Check for incorrect lengths (even without padding)
if (data.length % 4 === 1) {
throw new OperationError(`Error: Invalid Base64 input length (${data.length}). Cannot be 4n+1, even without padding chars.`);
}
if (alphabet.length === 65) { // Padding character included
const pad = alphabet.charAt(64);
const padPos = data.indexOf(pad);
if (padPos >= 0) {
// Check that the padding character is only used at the end and maximum of twice
if (padPos < data.length - 2 || data.charAt(data.length - 1) !== pad) {
throw new OperationError(`Error: Base64 padding character (${pad}) not used in the correct place.`);
}
// Check that input is padded to the correct length
if (data.length % 4 !== 0) {
throw new OperationError("Error: Base64 not padded to a multiple of 4.");
}
}
}
}
const output = [];
@@ -98,31 +129,27 @@ export function fromBase64(data, alphabet="A-Za-z0-9+/=", returnType="string", r
enc1, enc2, enc3, enc4,
i = 0;
if (removeNonAlphChars) {
const re = new RegExp("[^" + alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g");
data = data.replace(re, "");
}
while (i < data.length) {
enc1 = alphabet.indexOf(data.charAt(i++));
enc2 = alphabet.indexOf(data.charAt(i++) || "=");
enc3 = alphabet.indexOf(data.charAt(i++) || "=");
enc4 = alphabet.indexOf(data.charAt(i++) || "=");
enc2 = alphabet.indexOf(data.charAt(i++));
enc3 = alphabet.indexOf(data.charAt(i++));
enc4 = alphabet.indexOf(data.charAt(i++));
enc2 = enc2 === -1 ? 64 : enc2;
enc3 = enc3 === -1 ? 64 : enc3;
enc4 = enc4 === -1 ? 64 : enc4;
if (strictMode && (enc1 < 0 || enc2 < 0 || enc3 < 0 || enc4 < 0)) {
throw new OperationError("Error: Base64 input contains non-alphabet char(s)");
}
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
output.push(chr1);
if (enc3 !== 64) {
if (chr1 < 256) {
output.push(chr1);
}
if (chr2 < 256 && enc3 !== 64) {
output.push(chr2);
}
if (enc4 !== 64) {
if (chr3 < 256 && enc4 !== 64) {
output.push(chr3);
}
}
@@ -148,4 +175,8 @@ export const ALPHABET_OPTIONS = [
{name: "BinHex: !-,-0-689@A-NP-VX-Z[`a-fh-mp-r", value: "!-,-0-689@A-NP-VX-Z[`a-fh-mp-r"},
{name: "ROT13: N-ZA-Mn-za-m0-9+/=", value: "N-ZA-Mn-za-m0-9+/="},
{name: "UNIX crypt: ./0-9A-Za-z", value: "./0-9A-Za-z"},
{name: "Atom128: /128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC", value: "/128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC"},
{name: "Megan35: 3GHIJKLMNOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5", value: "3GHIJKLMNOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5"},
{name: "Zong22: ZKj9n+yf0wDVX1s/5YbdxSo=ILaUpPBCHg8uvNO4klm6iJGhQ7eFrWczAMEq3RTt2", value: "ZKj9n+yf0wDVX1s/5YbdxSo=ILaUpPBCHg8uvNO4klm6iJGhQ7eFrWczAMEq3RTt2"},
{name: "Hazz15: HNO4klm6ij9n+J2hyf0gzA8uvwDEq3X1Q7ZKeFrWcVTts/MRGYbdxSo=ILaUpPBC5", value: "HNO4klm6ij9n+J2hyf0gzA8uvwDEq3X1Q7ZKeFrWcVTts/MRGYbdxSo=ILaUpPBC5"}
];

View File

@@ -32,9 +32,9 @@ export const ALPHABET_OPTIONS = [
* @returns {string}
*/
export function alphabetName(alphabet) {
alphabet = alphabet.replace("'", "&apos;");
alphabet = alphabet.replace("\"", "&quot;");
alphabet = alphabet.replace("\\", "&bsol;");
alphabet = alphabet.replace(/'/g, "&apos;");
alphabet = alphabet.replace(/"/g, "&quot;");
alphabet = alphabet.replace(/\\/g, "&bsol;");
let name;
ALPHABET_OPTIONS.forEach(function(a) {

View File

@@ -7,38 +7,45 @@
*/
import Utils from "../Utils.mjs";
import OperationError from "../errors/OperationError.mjs";
/**
* Convert a byte array into a binary string.
*
* @param {Uint8Array|byteArray} data
* @param {Uint8Array|byteArray|number} data
* @param {string} [delim="Space"]
* @param {number} [padding=8]
* @returns {string}
*
* @example
* // returns "00010000 00100000 00110000"
* // returns "00001010 00010100 00011110"
* toBinary([10,20,30]);
*
* // returns "00010000 00100000 00110000"
* toBinary([10,20,30], ":");
* // returns "00001010:00010100:00011110"
* toBinary([10,20,30], "Colon");
*
* // returns "1010:10100:11110"
* toBinary([10,20,30], "Colon", 0);
*/
export function toBinary(data, delim="Space", padding=8) {
if (!data) return "";
if (data === undefined || data === null)
throw new OperationError("Unable to convert to binary: Empty input data enocuntered");
delim = Utils.charRep(delim);
let output = "";
for (let i = 0; i < data.length; i++) {
output += data[i].toString(2).padStart(padding, "0") + delim;
}
if (delim.length) {
return output.slice(0, -delim.length);
if (data.length) { // array
for (let i = 0; i < data.length; i++) {
output += data[i].toString(2).padStart(padding, "0");
if (i !== data.length - 1) output += delim;
}
} else if (typeof data === "number") { // Single value
return data.toString(2).padStart(padding, "0");
} else {
return output;
return "";
}
return output;
}
@@ -52,12 +59,15 @@ export function toBinary(data, delim="Space", padding=8) {
*
* @example
* // returns [10,20,30]
* fromBinary("00010000 00100000 00110000");
* fromBinary("00001010 00010100 00011110");
*
* // returns [10,20,30]
* fromBinary("00010000:00100000:00110000", "Colon");
* fromBinary("00001010:00010100:00011110", "Colon");
*/
export function fromBinary(data, delim="Space", byteLen=8) {
if (byteLen < 1 || Math.round(byteLen) !== byteLen)
throw new OperationError("Byte length must be a positive integer");
const delimRegex = Utils.regexRep(delim);
data = data.replace(delimRegex, "");

View File

@@ -34,10 +34,10 @@ export function bitOp (input, key, func, nullPreserving, scheme) {
!(nullPreserving && (o === 0 || o === k))) {
switch (scheme) {
case "Input differential":
key[i % key.length] = x;
key[i % key.length] = o;
break;
case "Output differential":
key[i % key.length] = o;
key[i % key.length] = x;
break;
}
}

View File

@@ -6,6 +6,7 @@
*/
import OperationError from "../errors/OperationError.mjs";
import Utils from "../Utils.mjs";
/**
* @constant
@@ -86,8 +87,8 @@ export function getScatterValues(input, recordDelimiter, fieldDelimiter, columnH
}
values = values.map(row => {
const x = parseFloat(row[0], 10),
y = parseFloat(row[1], 10);
const x = parseFloat(row[0]),
y = parseFloat(row[1]);
if (Number.isNaN(x)) throw new OperationError("Values must be numbers in base 10.");
if (Number.isNaN(y)) throw new OperationError("Values must be numbers in base 10.");
@@ -121,14 +122,14 @@ export function getScatterValuesWithColour(input, recordDelimiter, fieldDelimite
}
values = values.map(row => {
const x = parseFloat(row[0], 10),
y = parseFloat(row[1], 10),
const x = parseFloat(row[0]),
y = parseFloat(row[1]),
colour = row[2];
if (Number.isNaN(x)) throw new OperationError("Values must be numbers in base 10.");
if (Number.isNaN(y)) throw new OperationError("Values must be numbers in base 10.");
return [x, y, colour];
return [x, y, Utils.escapeHtml(colour)];
});
return { headings, values };
@@ -157,7 +158,7 @@ export function getSeriesValues(input, recordDelimiter, fieldDelimiter, columnHe
values.forEach(row => {
const serie = row[0],
xVal = row[1],
val = parseFloat(row[2], 10);
val = parseFloat(row[2]);
if (Number.isNaN(val)) throw new OperationError("Values must be numbers in base 10.");

9
src/core/lib/Crypt.mjs Normal file
View File

@@ -0,0 +1,9 @@
/**
* Crypt resources.
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2021
* @license Apache-2.0
*/
export const cryptNotice = "WARNING: Cryptographic operations in CyberChef should not be relied upon to provide security in any situation. No guarantee is offered for their correctness. We advise you not to use keys generated from CyberChef in operational contexts.";

View File

@@ -12,15 +12,15 @@
*
* @param {string} input
* @param {RegExp} searchRegex
* @param {RegExp} removeRegex - A regular expression defining results to remove from the
* @param {RegExp} [removeRegex=null] - A regular expression defining results to remove from the
* final list
* @param {boolean} includeTotal - Whether or not to include the total number of results
* @param {Function} [sortBy=null] - The sorting comparison function to apply
* @param {boolean} [unique=false] - Whether to unique the results
* @returns {string}
*/
export function search (input, searchRegex, removeRegex, includeTotal) {
let output = "",
total = 0,
match;
export function search(input, searchRegex, removeRegex=null, sortBy=null, unique=false) {
let results = [];
let match;
while ((match = searchRegex.exec(input))) {
// Moves pointer when an empty string is matched (prevents infinite loop)
@@ -30,14 +30,19 @@ export function search (input, searchRegex, removeRegex, includeTotal) {
if (removeRegex && removeRegex.test(match[0]))
continue;
total++;
output += match[0] + "\n";
results.push(match[0]);
}
if (includeTotal)
output = "Total found: " + total + "\n\n" + output;
if (sortBy) {
results = results.sort(sortBy);
}
return output;
if (unique) {
results = results.unique();
}
return results;
}

View File

@@ -2197,14 +2197,14 @@ export const FILE_SIGNATURES = {
mime: "application/octet-stream",
description: "",
signature: {
0: 0x6b, // keych
0: 0x6b, // kych
1: 0x79,
2: 0x63,
3: 0x68,
4: 0x00,
5: 0x01
},
extractor: null
extractor: extractMacOSXKeychain
},
{
name: "TCP Packet",
@@ -2355,6 +2355,12 @@ export const FILE_SIGNATURES = {
1: 0x03,
2: 0xc6,
3: 0x04
},
{
0: 0x95,
1: 0x05,
2: 0x86,
3: 0x04
}
],
extractor: null
@@ -3406,6 +3412,26 @@ export function extractPListXML(bytes, offset) {
}
/**
* MacOS X Keychain Extactor.
*
* @param {Uint8Array} bytes
* @param {number} offset
* @returns {Uint8Array}
*/
export function extractMacOSXKeychain(bytes, offset) {
const stream = new Stream(bytes.slice(offset));
// Move to size field.
stream.moveTo(0x14);
// Move forwards by size.
stream.moveForwardsBy(stream.readInt(4));
return stream.carve();
}
/**
* OLE2 extractor.
*
@@ -3752,8 +3778,8 @@ function parseDEFLATE(stream) {
while (!finalBlock) {
// Read header
finalBlock = stream.readBits(1);
const blockType = stream.readBits(2);
finalBlock = stream.readBits(1, "le");
const blockType = stream.readBits(2, "le");
if (blockType === 0) {
/* No compression */
@@ -3772,16 +3798,16 @@ function parseDEFLATE(stream) {
/* Dynamic Huffman */
// Read the number of liternal and length codes
const hlit = stream.readBits(5) + 257;
const hlit = stream.readBits(5, "le") + 257;
// Read the number of distance codes
const hdist = stream.readBits(5) + 1;
const hdist = stream.readBits(5, "le") + 1;
// Read the number of code lengths
const hclen = stream.readBits(4) + 4;
const hclen = stream.readBits(4, "le") + 4;
// Parse code lengths
const codeLengths = new Uint8Array(huffmanOrder.length);
for (let i = 0; i < hclen; i++) {
codeLengths[huffmanOrder[i]] = stream.readBits(3);
codeLengths[huffmanOrder[i]] = stream.readBits(3, "le");
}
// Parse length table
@@ -3793,16 +3819,16 @@ function parseDEFLATE(stream) {
code = readHuffmanCode(stream, codeLengthsTable);
switch (code) {
case 16:
repeat = 3 + stream.readBits(2);
repeat = 3 + stream.readBits(2, "le");
while (repeat--) lengthTable[i++] = prev;
break;
case 17:
repeat = 3 + stream.readBits(3);
repeat = 3 + stream.readBits(3, "le");
while (repeat--) lengthTable[i++] = 0;
prev = 0;
break;
case 18:
repeat = 11 + stream.readBits(7);
repeat = 11 + stream.readBits(7, "le");
while (repeat--) lengthTable[i++] = 0;
prev = 0;
break;
@@ -3860,11 +3886,11 @@ function parseHuffmanBlock(stream, litTab, distTab) {
if (code < 256) continue;
// Length code
stream.readBits(lengthExtraTable[code - 257]);
stream.readBits(lengthExtraTable[code - 257], "le");
// Dist code
code = readHuffmanCode(stream, distTab);
stream.readBits(distanceExtraTable[code]);
stream.readBits(distanceExtraTable[code], "le");
}
}
@@ -3922,7 +3948,7 @@ function readHuffmanCode(stream, table) {
const [codeTable, maxCodeLength] = table;
// Read max length
const bitsBuf = stream.readBits(maxCodeLength);
const bitsBuf = stream.readBits(maxCodeLength, "le");
const codeWithLength = codeTable[bitsBuf & ((1 << maxCodeLength) - 1)];
const codeLength = codeWithLength >>> 16;

View File

@@ -213,7 +213,7 @@ function locatePotentialSig(buf, sig, offset) {
export function isType(type, buf) {
const types = detectFileType(buf);
if (!(types && types.length)) return false;
if (!types.length) return false;
if (typeof type === "string") {
return types.reduce((acc, t) => {

253
src/core/lib/FuzzyMatch.mjs Normal file
View File

@@ -0,0 +1,253 @@
/**
* LICENSE
*
* This software is dual-licensed to the public domain and under the following
* license: you are granted a perpetual, irrevocable license to copy, modify,
* publish, and distribute this file as you see fit.
*
* VERSION
* 0.1.0 (2016-03-28) Initial release
*
* AUTHOR
* Forrest Smith
*
* CONTRIBUTORS
* J<>rgen Tjern<72> - async helper
* Anurag Awasthi - updated to 0.2.0
*/
export const DEFAULT_WEIGHTS = {
sequentialBonus: 15, // bonus for adjacent matches
separatorBonus: 30, // bonus if match occurs after a separator
camelBonus: 30, // bonus if match is uppercase and prev is lower
firstLetterBonus: 15, // bonus if the first letter is matched
leadingLetterPenalty: -5, // penalty applied for every letter in str before the first match
maxLeadingLetterPenalty: -15, // maximum penalty for leading letters
unmatchedLetterPenalty: -1
};
/**
* Does a fuzzy search to find pattern inside a string.
* @param {string} pattern pattern to search for
* @param {string} str string which is being searched
* @param {boolean} global whether to search for all matches or just one
* @returns [boolean, number] a boolean which tells if pattern was
* found or not and a search score
*/
export function fuzzyMatch(pattern, str, global=false, weights=DEFAULT_WEIGHTS) {
const recursionCount = 0;
const recursionLimit = 10;
const matches = [];
const maxMatches = 256;
if (!global) {
return fuzzyMatchRecursive(
pattern,
str,
0 /* patternCurIndex */,
0 /* strCurrIndex */,
null /* srcMatches */,
matches,
maxMatches,
0 /* nextMatch */,
recursionCount,
recursionLimit,
weights
);
}
// Return all matches
let foundMatch = true,
score,
idxs,
strCurrIndex = 0;
const results = [];
while (foundMatch) {
[foundMatch, score, idxs] = fuzzyMatchRecursive(
pattern,
str,
0 /* patternCurIndex */,
strCurrIndex,
null /* srcMatches */,
matches,
maxMatches,
0 /* nextMatch */,
recursionCount,
recursionLimit,
weights
);
if (foundMatch) results.push([foundMatch, score, [...idxs]]);
strCurrIndex = idxs[idxs.length - 1] + 1;
}
return results;
}
/**
* Recursive helper function
*/
function fuzzyMatchRecursive(
pattern,
str,
patternCurIndex,
strCurrIndex,
srcMatches,
matches,
maxMatches,
nextMatch,
recursionCount,
recursionLimit,
weights
) {
let outScore = 0;
// Return if recursion limit is reached.
if (++recursionCount >= recursionLimit) {
return [false, outScore, []];
}
// Return if we reached ends of strings.
if (patternCurIndex === pattern.length || strCurrIndex === str.length) {
return [false, outScore, []];
}
// Recursion params
let recursiveMatch = false;
let bestRecursiveMatches = [];
let bestRecursiveScore = 0;
// Loop through pattern and str looking for a match.
let firstMatch = true;
while (patternCurIndex < pattern.length && strCurrIndex < str.length) {
// Match found.
if (
pattern[patternCurIndex].toLowerCase() === str[strCurrIndex].toLowerCase()
) {
if (nextMatch >= maxMatches) {
return [false, outScore, []];
}
if (firstMatch && srcMatches) {
matches = [...srcMatches];
firstMatch = false;
}
const [matched, recursiveScore, recursiveMatches] = fuzzyMatchRecursive(
pattern,
str,
patternCurIndex,
strCurrIndex + 1,
matches,
recursiveMatches,
maxMatches,
nextMatch,
recursionCount,
recursionLimit,
weights
);
if (matched) {
// Pick best recursive score.
if (!recursiveMatch || recursiveScore > bestRecursiveScore) {
bestRecursiveMatches = [...recursiveMatches];
bestRecursiveScore = recursiveScore;
}
recursiveMatch = true;
}
matches[nextMatch++] = strCurrIndex;
++patternCurIndex;
}
++strCurrIndex;
}
const matched = patternCurIndex === pattern.length;
if (matched) {
outScore = 100;
// Apply leading letter penalty
let penalty = weights.leadingLetterPenalty * matches[0];
penalty =
penalty < weights.maxLeadingLetterPenalty ?
weights.maxLeadingLetterPenalty :
penalty;
outScore += penalty;
// Apply unmatched penalty
const unmatched = str.length - nextMatch;
outScore += weights.unmatchedLetterPenalty * unmatched;
// Apply ordering bonuses
for (let i = 0; i < nextMatch; i++) {
const currIdx = matches[i];
if (i > 0) {
const prevIdx = matches[i - 1];
if (currIdx === prevIdx + 1) {
outScore += weights.sequentialBonus;
}
}
// Check for bonuses based on neighbor character value.
if (currIdx > 0) {
// Camel case
const neighbor = str[currIdx - 1];
const curr = str[currIdx];
if (
neighbor !== neighbor.toUpperCase() &&
curr !== curr.toLowerCase()
) {
outScore += weights.camelBonus;
}
const isNeighbourSeparator = neighbor === "_" || neighbor === " ";
if (isNeighbourSeparator) {
outScore += weights.separatorBonus;
}
} else {
// First letter
outScore += weights.firstLetterBonus;
}
}
// Return best result
if (recursiveMatch && (!matched || bestRecursiveScore > outScore)) {
// Recursive score is better than "this"
matches = bestRecursiveMatches;
outScore = bestRecursiveScore;
return [true, outScore, matches];
} else if (matched) {
// "this" score is better than recursive
return [true, outScore, matches];
} else {
return [false, outScore, matches];
}
}
return [false, outScore, matches];
}
/**
* Turns a list of match indexes into a list of match ranges
*
* @author n1474335 [n1474335@gmail.com]
* @param [number] matches
* @returns [[number]]
*/
export function calcMatchRanges(matches) {
const ranges = [];
let start = matches[0],
curr = start;
matches.forEach(m => {
if (m === curr || m === curr + 1) curr = m;
else {
ranges.push([start, curr - start + 1]);
start = m;
curr = m;
}
});
ranges.push([start, curr - start + 1]);
return ranges;
}

View File

@@ -7,6 +7,7 @@
*/
import Utils from "../Utils.mjs";
import OperationError from "../errors/OperationError.mjs";
/**
@@ -100,6 +101,9 @@ export function toHexFast(data) {
* fromHex("0a:14:1e", "Colon");
*/
export function fromHex(data, delim="Auto", byteLen=2) {
if (byteLen < 1 || Math.round(byteLen) !== byteLen)
throw new OperationError("Byte length must be a positive integer");
if (delim !== "None") {
const delimRegex = delim === "Auto" ? /[^a-f\d]|(0x)/gi : Utils.regexRep(delim);
data = data.replace(delimRegex, "");

24
src/core/lib/JWT.mjs Normal file
View File

@@ -0,0 +1,24 @@
/**
* JWT resources
*
* @author mt3571 [mt3571@protonmail.com]
* @copyright Crown Copyright 2020
* @license Apache-2.0
*/
/**
* List of the JWT algorithms that can be used
*/
export const JWT_ALGORITHMS = [
"HS256",
"HS384",
"HS512",
"RS256",
"RS384",
"RS512",
"ES256",
"ES384",
"ES512",
"None"
];

View File

@@ -1,4 +1,4 @@
import OperationConfig from "../config/OperationConfig.json";
import OperationConfig from "../config/OperationConfig.json" assert {type: "json"};
import Utils, { isWorkerEnvironment } from "../Utils.mjs";
import Recipe from "../Recipe.mjs";
import Dish from "../Dish.mjs";

View File

@@ -1,4 +1,5 @@
import Utils from "../Utils.mjs";
import protobuf from "protobufjs";
/**
* Protobuf lib. Contains functions to decode protobuf serialised
@@ -32,9 +33,10 @@ class Protobuf {
this.MSB = 0x80;
this.VALUE = 0x7f;
// Declare offset and length
// Declare offset, length, and field type object
this.offset = 0;
this.LENGTH = data.length;
this.fieldTypes = {};
}
// Public Functions
@@ -76,15 +78,281 @@ class Protobuf {
return pb._varInt();
}
/**
* Encode input JSON according to the given schema
*
* @param {Object} input
* @param {Object []} args
* @returns {Object}
*/
static encode(input, args) {
this.updateProtoRoot(args[0]);
if (!this.mainMessageName) {
throw new Error("Schema Error: Schema not defined");
}
const message = this.parsedProto.root.nested[this.mainMessageName];
// Convert input into instance of message, and verify instance
input = message.fromObject(input);
const error = message.verify(input);
if (error) {
throw new Error("Input Error: " + error);
}
// Encode input
const output = message.encode(input).finish();
return new Uint8Array(output).buffer;
}
/**
* Parse Protobuf data
*
* @param {byteArray} input
* @returns {Object}
*/
static decode(input) {
static decode(input, args) {
this.updateProtoRoot(args[0]);
this.showUnknownFields = args[1];
this.showTypes = args[2];
return this.mergeDecodes(input);
}
/**
* Update the parsedProto, throw parsing errors
*
* @param {string} protoText
*/
static updateProtoRoot(protoText) {
try {
this.parsedProto = protobuf.parse(protoText);
if (this.parsedProto.package) {
this.parsedProto.root = this.parsedProto.root.nested[this.parsedProto.package];
}
this.updateMainMessageName();
} catch (error) {
throw new Error("Schema " + error);
}
}
/**
* Set mainMessageName to the first instance of a message defined in the schema that is not a submessage
*
*/
static updateMainMessageName() {
const messageNames = [];
const fieldTypes = [];
this.parsedProto.root.nestedArray.forEach(block => {
if (block instanceof protobuf.Type) {
messageNames.push(block.name);
this.parsedProto.root.nested[block.name].fieldsArray.forEach(field => {
fieldTypes.push(field.type);
});
}
});
if (messageNames.length === 0) {
this.mainMessageName = null;
} else {
// for (const name of messageNames) {
// if (!fieldTypes.includes(name)) {
// this.mainMessageName = name;
// break;
// }
// }
this.mainMessageName = messageNames[0];
}
}
/**
* Decode input using Protobufjs package and raw methods, compare, and merge results
*
* @param {byteArray} input
* @returns {Object}
*/
static mergeDecodes(input) {
const pb = new Protobuf(input);
return pb._parse();
let rawDecode = pb._parse();
let message;
if (this.showTypes) {
rawDecode = this.showRawTypes(rawDecode, pb.fieldTypes);
this.parsedProto.root = this.appendTypesToFieldNames(this.parsedProto.root);
}
try {
message = this.parsedProto.root.nested[this.mainMessageName];
const packageDecode = message.toObject(message.decode(input), {
bytes: String,
longs: Number,
enums: String,
defualts: true
});
const output = {};
if (this.showUnknownFields) {
output[message.name] = packageDecode;
output["Unknown Fields"] = this.compareFields(rawDecode, message);
return output;
} else {
return packageDecode;
}
} catch (error) {
if (message) {
throw new Error("Input " + error);
} else {
return rawDecode;
}
}
}
/**
* Replace fieldnames with fieldname and type
*
* @param {Object} schemaRoot
* @returns {Object}
*/
static appendTypesToFieldNames(schemaRoot) {
for (const block of schemaRoot.nestedArray) {
if (block instanceof protobuf.Type) {
for (const [fieldName, fieldData] of Object.entries(block.fields)) {
schemaRoot.nested[block.name].remove(block.fields[fieldName]);
schemaRoot.nested[block.name].add(new protobuf.Field(`${fieldName} (${fieldData.type})`, fieldData.id, fieldData.type, fieldData.rule));
}
}
}
return schemaRoot;
}
/**
* Add field type to field name for fields in the raw decoded output
*
* @param {Object} rawDecode
* @param {Object} fieldTypes
* @returns {Object}
*/
static showRawTypes(rawDecode, fieldTypes) {
for (const [fieldNum, value] of Object.entries(rawDecode)) {
const fieldType = fieldTypes[fieldNum];
let outputFieldValue;
let outputFieldType;
// Submessages
if (isNaN(fieldType)) {
outputFieldType = 2;
// Repeated submessages
if (Array.isArray(value)) {
const fieldInstances = [];
for (const instance of Object.keys(value)) {
if (typeof(value[instance]) !== "string") {
fieldInstances.push(this.showRawTypes(value[instance], fieldType));
} else {
fieldInstances.push(value[instance]);
}
}
outputFieldValue = fieldInstances;
// Single submessage
} else {
outputFieldValue = this.showRawTypes(value, fieldType);
}
// Non-submessage field
} else {
outputFieldType = fieldType;
outputFieldValue = value;
}
// Substitute fieldNum with field number and type
rawDecode[`field #${fieldNum}: ${this.getTypeInfo(outputFieldType)}`] = outputFieldValue;
delete rawDecode[fieldNum];
}
return rawDecode;
}
/**
* Compare raw decode to package decode and return discrepancies
*
* @param rawDecodedMessage
* @param schemaMessage
* @returns {Object}
*/
static compareFields(rawDecodedMessage, schemaMessage) {
// Define message data using raw decode output and schema
const schemaFieldProperties = {};
const schemaFieldNames = Object.keys(schemaMessage.fields);
schemaFieldNames.forEach(field => schemaFieldProperties[schemaMessage.fields[field].id] = field);
// Loop over each field present in the raw decode output
for (const fieldName in rawDecodedMessage) {
let fieldId;
if (isNaN(fieldName)) {
fieldId = fieldName.match(/^field #(\d+)/)[1];
} else {
fieldId = fieldName;
}
// Check if this field is defined in the schema
if (fieldId in schemaFieldProperties) {
const schemaFieldName = schemaFieldProperties[fieldId];
// Extract the current field data from the raw decode and schema
const rawFieldData = rawDecodedMessage[fieldName];
const schemaField = schemaMessage.fields[schemaFieldName];
// Check for repeated fields
if (Array.isArray(rawFieldData) && !schemaField.repeated) {
rawDecodedMessage[`(${schemaMessage.name}) ${schemaFieldName} is a repeated field`] = rawFieldData;
}
// Check for submessage fields
if (schemaField.resolvedType instanceof protobuf.Type) {
const subMessageType = schemaMessage.fields[schemaFieldName].type;
const schemaSubMessage = this.parsedProto.root.nested[subMessageType];
const rawSubMessages = rawDecodedMessage[fieldName];
let rawDecodedSubMessage = {};
// Squash multiple submessage instances into one submessage
if (Array.isArray(rawSubMessages)) {
rawSubMessages.forEach(subMessageInstance => {
const instanceFields = Object.entries(subMessageInstance);
instanceFields.forEach(subField => {
rawDecodedSubMessage[subField[0]] = subField[1];
});
});
} else {
rawDecodedSubMessage = rawSubMessages;
}
// Treat submessage as own message and compare its fields
rawDecodedSubMessage = Protobuf.compareFields(rawDecodedSubMessage, schemaSubMessage);
if (Object.entries(rawDecodedSubMessage).length !== 0) {
rawDecodedMessage[`${schemaFieldName} (${subMessageType}) has missing fields`] = rawDecodedSubMessage;
}
}
delete rawDecodedMessage[fieldName];
}
}
return rawDecodedMessage;
}
/**
* Returns wiretype information for input wiretype number
*
* @param {number} wireType
* @returns {string}
*/
static getTypeInfo(wireType) {
switch (wireType) {
case 0:
return "VarInt (e.g. int32, bool)";
case 1:
return "64-Bit (e.g. fixed64, double)";
case 2:
return "L-delim (e.g. string, message)";
case 5:
return "32-Bit (e.g. fixed32, float)";
}
}
// Private Class Functions
@@ -143,6 +411,11 @@ class Protobuf {
const header = this._fieldHeader();
const type = header.type;
const key = header.key;
if (typeof(this.fieldTypes[key]) !== "object") {
this.fieldTypes[key] = type;
}
switch (type) {
// varint
case 0:
@@ -152,7 +425,7 @@ class Protobuf {
return { "key": key, "value": this._uint64() };
// length delimited
case 2:
return { "key": key, "value": this._lenDelim() };
return { "key": key, "value": this._lenDelim(key) };
// fixed 32
case 5:
return { "key": key, "value": this._uint32() };
@@ -237,10 +510,10 @@ class Protobuf {
* @returns {number}
*/
_uint64() {
// Read off a Uint64
let num = this.data[this.offset++] * 0x1000000 + (this.data[this.offset++] << 16) + (this.data[this.offset++] << 8) + this.data[this.offset++];
num = num * 0x100000000 + this.data[this.offset++] * 0x1000000 + (this.data[this.offset++] << 16) + (this.data[this.offset++] << 8) + this.data[this.offset++];
return num;
// Read off a Uint64 with little-endian
const lowerHalf = this.data[this.offset++] + (this.data[this.offset++] * 0x100) + (this.data[this.offset++] * 0x10000) + this.data[this.offset++] * 0x1000000;
const upperHalf = this.data[this.offset++] + (this.data[this.offset++] * 0x100) + (this.data[this.offset++] * 0x10000) + this.data[this.offset++] * 0x1000000;
return upperHalf * 0x100000000 + lowerHalf;
}
/**
@@ -249,7 +522,7 @@ class Protobuf {
* @private
* @returns {Object|string}
*/
_lenDelim() {
_lenDelim(fieldNum) {
// Read off the field length
const length = this._varInt();
const fieldBytes = this.data.slice(this.offset, this.offset + length);
@@ -258,6 +531,10 @@ class Protobuf {
// Attempt to parse as a new Protobuf Object
const pbObject = new Protobuf(fieldBytes);
field = pbObject._parse();
// Set field types object
this.fieldTypes[fieldNum] = {...this.fieldTypes[fieldNum], ...pbObject.fieldTypes};
} catch (err) {
// Otherwise treat as bytes
field = Utils.byteArrayToChars(fieldBytes);
@@ -276,7 +553,7 @@ class Protobuf {
_uint32() {
// Use a dataview to read off the integer
const dataview = new DataView(new Uint8Array(this.data.slice(this.offset, this.offset + 4)).buffer);
const value = dataview.getUint32(0);
const value = dataview.getUint32(0, true);
this.offset += 4;
return value;
}

47
src/core/lib/Protocol.mjs Normal file
View File

@@ -0,0 +1,47 @@
/**
* Protocol parsing functions.
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2022
* @license Apache-2.0
*/
import BigNumber from "bignumber.js";
import {toHexFast} from "../lib/Hex.mjs";
/**
* Recursively displays a JSON object as an HTML table
*
* @param {Object} obj
* @returns string
*/
export function objToTable(obj, nested=false) {
let html = `<table
class='table table-sm table-nonfluid ${nested ? "mb-0 table-borderless" : "table-bordered"}'
style='table-layout: fixed; ${nested ? "margin: -1px !important;" : ""}'>`;
if (!nested)
html += `<tr>
<th>Field</th>
<th>Value</th>
</tr>`;
for (const key in obj) {
html += `<tr><td style='word-wrap: break-word'>${key}</td>`;
if (typeof obj[key] === "object")
html += `<td style='padding: 0'>${objToTable(obj[key], true)}</td>`;
else
html += `<td>${obj[key]}</td>`;
html += "</tr>";
}
html += "</table>";
return html;
}
/**
* Converts bytes into a BigNumber string
* @param {Uint8Array} bs
* @returns {string}
*/
export function bytesToLargeNumber(bs) {
return BigNumber(toHexFast(bs), 16).toString();
}

View File

@@ -15,7 +15,7 @@ import { toHex, fromHex } from "./Hex.mjs";
* @param {number} indent
* @returns {string}
*/
export function formatDnStr (dnStr, indent) {
export function formatDnStr(dnStr, indent) {
const fields = dnStr.substr(1).replace(/([^\\])\//g, "$1$1/").split(/[^\\]\//);
let output = "",
maxKeyLen = 0,
@@ -54,7 +54,7 @@ export function formatDnStr (dnStr, indent) {
* @param {number} indent
* @returns {string}
*/
export function formatByteStr (byteStr, length, indent) {
export function formatByteStr(byteStr, length, indent) {
byteStr = toHex(fromHex(byteStr), ":");
length = length * 3;
let output = "";

17
src/core/lib/RSA.mjs Normal file
View File

@@ -0,0 +1,17 @@
/**
* RSA resources.
*
* @author Matt C [me@mitt.dev]
* @copyright Crown Copyright 2021
* @license Apache-2.0
*/
import forge from "node-forge";
export const MD_ALGORITHMS = {
"SHA-1": forge.md.sha1,
"MD5": forge.md.md5,
"SHA-256": forge.md.sha256,
"SHA-384": forge.md.sha384,
"SHA-512": forge.md.sha512,
};

502
src/core/lib/SIGABA.mjs Normal file
View File

@@ -0,0 +1,502 @@
/**
* Emulation of the SIGABA machine
*
* @author hettysymes
* @copyright hettysymes 2020
* @license Apache-2.0
*/
/**
* A set of randomised example SIGABA cipher/control rotors (these rotors are interchangeable). Cipher and control rotors can be referred to as C and R rotors respectively.
*/
export const CR_ROTORS = [
{name: "Example 1", value: "SRGWANHPJZFXVIDQCEUKBYOLMT"},
{name: "Example 2", value: "THQEFSAZVKJYULBODCPXNIMWRG"},
{name: "Example 3", value: "XDTUYLEVFNQZBPOGIRCSMHWKAJ"},
{name: "Example 4", value: "LOHDMCWUPSTNGVXYFJREQIKBZA"},
{name: "Example 5", value: "ERXWNZQIJYLVOFUMSGHTCKPBDA"},
{name: "Example 6", value: "FQECYHJIOUMDZVPSLKRTGWXBAN"},
{name: "Example 7", value: "TBYIUMKZDJSOPEWXVANHLCFQGR"},
{name: "Example 8", value: "QZUPDTFNYIAOMLEBWJXCGHKRSV"},
{name: "Example 9", value: "CZWNHEMPOVXLKRSIDGJFYBTQAU"},
{name: "Example 10", value: "ENPXJVKYQBFZTICAGMOHWRLDUS"}
];
/**
* A set of randomised example SIGABA index rotors (may be referred to as I rotors).
*/
export const I_ROTORS = [
{name: "Example 1", value: "6201348957"},
{name: "Example 2", value: "6147253089"},
{name: "Example 3", value: "8239647510"},
{name: "Example 4", value: "7194835260"},
{name: "Example 5", value: "4873205916"}
];
export const NUMBERS = "0123456789".split("");
/**
* Converts a letter to uppercase (if it already isn't)
*
* @param {char} letter - letter to convert to uppercase
* @returns {char}
*/
export function convToUpperCase(letter) {
const charCode = letter.charCodeAt();
if (97<=charCode && charCode<=122) {
return String.fromCharCode(charCode-32);
}
return letter;
}
/**
* The SIGABA machine consisting of the 3 rotor banks: cipher, control and index banks.
*/
export class SigabaMachine {
/**
* SigabaMachine constructor
*
* @param {Object[]} cipherRotors - list of CRRotors
* @param {Object[]} controlRotors - list of CRRotors
* @param {object[]} indexRotors - list of IRotors
*/
constructor(cipherRotors, controlRotors, indexRotors) {
this.cipherBank = new CipherBank(cipherRotors);
this.controlBank = new ControlBank(controlRotors);
this.indexBank = new IndexBank(indexRotors);
}
/**
* Steps all the correct rotors in the machine.
*/
step() {
const controlOut = this.controlBank.goThroughControl();
const indexOut = this.indexBank.goThroughIndex(controlOut);
this.cipherBank.step(indexOut);
}
/**
* Encrypts a letter. A space is converted to a "Z" before encryption, and a "Z" is converted to an "X". This allows spaces to be encrypted.
*
* @param {char} letter - letter to encrypt
* @returns {char}
*/
encryptLetter(letter) {
letter = convToUpperCase(letter);
if (letter === " ") {
letter = "Z";
} else if (letter === "Z") {
letter = "X";
}
const encryptedLetter = this.cipherBank.encrypt(letter);
this.step();
return encryptedLetter;
}
/**
* Decrypts a letter. A letter decrypted as a "Z" is converted to a space before it is output, since spaces are converted to "Z"s before encryption.
*
* @param {char} letter - letter to decrypt
* @returns {char}
*/
decryptLetter(letter) {
letter = convToUpperCase(letter);
let decryptedLetter = this.cipherBank.decrypt(letter);
if (decryptedLetter === "Z") {
decryptedLetter = " ";
}
this.step();
return decryptedLetter;
}
/**
* Encrypts a message of one or more letters
*
* @param {string} msg - message to encrypt
* @returns {string}
*/
encrypt(msg) {
let ciphertext = "";
for (const letter of msg) {
ciphertext = ciphertext.concat(this.encryptLetter(letter));
}
return ciphertext;
}
/**
* Decrypts a message of one or more letters
*
* @param {string} msg - message to decrypt
* @returns {string}
*/
decrypt(msg) {
let plaintext = "";
for (const letter of msg) {
plaintext = plaintext.concat(this.decryptLetter(letter));
}
return plaintext;
}
}
/**
* The cipher rotor bank consists of 5 cipher rotors in either a forward or reversed orientation.
*/
export class CipherBank {
/**
* CipherBank constructor
*
* @param {Object[]} rotors - list of CRRotors
*/
constructor(rotors) {
this.rotors = rotors;
}
/**
* Encrypts a letter through the cipher rotors (signal goes from left-to-right)
*
* @param {char} inputPos - the input position of the signal (letter to be encrypted)
* @returns {char}
*/
encrypt(inputPos) {
for (const rotor of this.rotors) {
inputPos = rotor.crypt(inputPos, "leftToRight");
}
return inputPos;
}
/**
* Decrypts a letter through the cipher rotors (signal goes from right-to-left)
*
* @param {char} inputPos - the input position of the signal (letter to be decrypted)
* @returns {char}
*/
decrypt(inputPos) {
const revOrderedRotors = [...this.rotors].reverse();
for (const rotor of revOrderedRotors) {
inputPos = rotor.crypt(inputPos, "rightToLeft");
}
return inputPos;
}
/**
* Step the cipher rotors forward according to the inputs from the index rotors
*
* @param {number[]} indexInputs - the inputs from the index rotors
*/
step(indexInputs) {
const logicDict = {0: [0, 9], 1: [7, 8], 2: [5, 6], 3: [3, 4], 4: [1, 2]};
const rotorsToMove = [];
for (const key in logicDict) {
const item = logicDict[key];
for (const i of indexInputs) {
if (item.includes(i)) {
rotorsToMove.push(this.rotors[key]);
break;
}
}
}
for (const rotor of rotorsToMove) {
rotor.step();
}
}
}
/**
* The control rotor bank consists of 5 control rotors in either a forward or reversed orientation. Signals to the control rotor bank always go from right-to-left.
*/
export class ControlBank {
/**
* ControlBank constructor. The rotors have been reversed as signals go from right-to-left through the control rotors.
*
* @param {Object[]} rotors - list of CRRotors
*/
constructor(rotors) {
this.rotors = [...rotors].reverse();
}
/**
* Encrypts a letter.
*
* @param {char} inputPos - the input position of the signal
* @returns {char}
*/
crypt(inputPos) {
for (const rotor of this.rotors) {
inputPos = rotor.crypt(inputPos, "rightToLeft");
}
return inputPos;
}
/**
* Gets the outputs of the control rotors. The inputs to the control rotors are always "F", "G", "H" and "I".
*
* @returns {number[]}
*/
getOutputs() {
const outputs = [this.crypt("F"), this.crypt("G"), this.crypt("H"), this.crypt("I")];
const logicDict = {1: "B", 2: "C", 3: "DE", 4: "FGH", 5: "IJK", 6: "LMNO", 7: "PQRST", 8: "UVWXYZ", 9: "A"};
const numberOutputs = [];
for (const key in logicDict) {
const item = logicDict[key];
for (const output of outputs) {
if (item.includes(output)) {
numberOutputs.push(key);
break;
}
}
}
return numberOutputs;
}
/**
* Steps the control rotors. Only 3 of the control rotors step: one after every encryption, one after every 26, and one after every 26 squared.
*/
step() {
const MRotor = this.rotors[1], FRotor = this.rotors[2], SRotor = this.rotors[3];
// 14 is the offset of "O" from "A" - the next rotor steps once the previous rotor reaches "O"
if (FRotor.state === 14) {
if (MRotor.state === 14) {
SRotor.step();
}
MRotor.step();
}
FRotor.step();
}
/**
* The goThroughControl function combines getting the outputs from the control rotor bank and then stepping them.
*
* @returns {number[]}
*/
goThroughControl() {
const outputs = this.getOutputs();
this.step();
return outputs;
}
}
/**
* The index rotor bank consists of 5 index rotors all placed in the forwards orientation.
*/
export class IndexBank {
/**
* IndexBank constructor
*
* @param {Object[]} rotors - list of IRotors
*/
constructor(rotors) {
this.rotors = rotors;
}
/**
* Encrypts a number.
*
* @param {number} inputPos - the input position of the signal
* @returns {number}
*/
crypt(inputPos) {
for (const rotor of this.rotors) {
inputPos = rotor.crypt(inputPos);
}
return inputPos;
}
/**
* The goThroughIndex function takes the inputs from the control rotor bank and returns the list of outputs after encryption through the index rotors.
*
* @param {number[]} controlInputs - inputs from the control rotors
* @returns {number[]}
*/
goThroughIndex(controlInputs) {
const outputs = [];
for (const inp of controlInputs) {
outputs.push(this.crypt(inp));
}
return outputs;
}
}
/**
* Rotor class
*/
export class Rotor {
/**
* Rotor constructor
*
* @param {number[]} wireSetting - the wirings within the rotor: mapping from left-to-right, the index of the number in the list maps onto the number at that index
* @param {bool} rev - true if the rotor is reversed, false if it isn't
* @param {number} key - the starting position or state of the rotor
*/
constructor(wireSetting, key, rev) {
this.state = key;
this.numMapping = this.getNumMapping(wireSetting, rev);
this.posMapping = this.getPosMapping(rev);
}
/**
* Get the number mapping from the wireSetting (only different from wireSetting if rotor is reversed)
*
* @param {number[]} wireSetting - the wirings within the rotors
* @param {bool} rev - true if reversed, false if not
* @returns {number[]}
*/
getNumMapping(wireSetting, rev) {
if (rev===false) {
return wireSetting;
} else {
const length = wireSetting.length;
const tempMapping = new Array(length);
for (let i=0; i<length; i++) {
tempMapping[wireSetting[i]] = i;
}
return tempMapping;
}
}
/**
* Get the position mapping (how the position numbers map onto the numbers of the rotor)
*
* @param {bool} rev - true if reversed, false if not
* @returns {number[]}
*/
getPosMapping(rev) {
const length = this.numMapping.length;
const posMapping = [];
if (rev===false) {
for (let i = this.state; i < this.state+length; i++) {
let res = i%length;
if (res<0) {
res += length;
}
posMapping.push(res);
}
} else {
for (let i = this.state; i > this.state-length; i--) {
let res = i%length;
if (res<0) {
res += length;
}
posMapping.push(res);
}
}
return posMapping;
}
/**
* Encrypt/decrypt data. This process is identical to the rotors of cipher machines such as Enigma or Typex.
*
* @param {number} inputPos - the input position of the signal (the data to encrypt/decrypt)
* @param {string} direction - one of "leftToRight" and "rightToLeft", states the direction in which the signal passes through the rotor
* @returns {number}
*/
cryptNum(inputPos, direction) {
const inpNum = this.posMapping[inputPos];
let outNum;
if (direction === "leftToRight") {
outNum = this.numMapping[inpNum];
} else if (direction === "rightToLeft") {
outNum = this.numMapping.indexOf(inpNum);
}
const outPos = this.posMapping.indexOf(outNum);
return outPos;
}
/**
* Steps the rotor. The number at position 0 will be moved to position 1 etc.
*/
step() {
const lastNum = this.posMapping.pop();
this.posMapping.splice(0, 0, lastNum);
this.state = this.posMapping[0];
}
}
/**
* A CRRotor is a cipher (C) or control (R) rotor. These rotors are identical and interchangeable. A C or R rotor consists of 26 contacts, one for each letter, and may be put into either a forwards of reversed orientation.
*/
export class CRRotor extends Rotor {
/**
* CRRotor constructor
*
* @param {string} wireSetting - the rotor wirings (string of letters)
* @param {char} key - initial state of rotor
* @param {bool} rev - true if reversed, false if not
*/
constructor(wireSetting, key, rev=false) {
wireSetting = wireSetting.split("").map(CRRotor.letterToNum);
super(wireSetting, CRRotor.letterToNum(key), rev);
}
/**
* Static function which converts a letter into its number i.e. its offset from the letter "A"
*
* @param {char} letter - letter to convert to number
* @returns {number}
*/
static letterToNum(letter) {
return letter.charCodeAt()-65;
}
/**
* Static function which converts a number (a letter's offset from "A") into its letter
*
* @param {number} num - number to convert to letter
* @returns {char}
*/
static numToLetter(num) {
return String.fromCharCode(num+65);
}
/**
* Encrypts/decrypts a letter.
*
* @param {char} inputPos - the input position of the signal ("A" refers to position 0 etc.)
* @param {string} direction - one of "leftToRight" and "rightToLeft"
* @returns {char}
*/
crypt(inputPos, direction) {
inputPos = CRRotor.letterToNum(inputPos);
const outPos = this.cryptNum(inputPos, direction);
return CRRotor.numToLetter(outPos);
}
}
/**
* An IRotor is an index rotor, which consists of 10 contacts each numbered from 0 to 9. Unlike C and R rotors, they cannot be put in the reversed orientation. The index rotors do not step at any point during encryption or decryption.
*/
export class IRotor extends Rotor {
/**
* IRotor constructor
*
* @param {string} wireSetting - the rotor wirings (string of numbers)
* @param {char} key - initial state of rotor
*/
constructor(wireSetting, key) {
wireSetting = wireSetting.split("").map(Number);
super(wireSetting, Number(key), false);
}
/**
* Encrypts a number
*
* @param {number} inputPos - the input position of the signal
* @returns {number}
*/
crypt(inputPos) {
return this.cryptNum(inputPos, "leftToRight");
}
}

331
src/core/lib/SM4.mjs Normal file
View File

@@ -0,0 +1,331 @@
/**
* Complete implementation of SM4 cipher encryption/decryption with
* ECB, CBC, CFB, OFB, CTR block modes.
* These modes are specified in IETF draft-ribose-cfrg-sm4-09, see:
* https://tools.ietf.org/id/draft-ribose-cfrg-sm4-09.html
* for details.
*
* Follows spec from Cryptography Standardization Technical Comittee:
* http://www.gmbz.org.cn/upload/2018-04-04/1522788048733065051.pdf
*
* @author swesven
* @copyright 2021
* @license Apache-2.0
*/
import OperationError from "../errors/OperationError.mjs";
/** Number of rounds */
const NROUNDS = 32;
/** block size in bytes */
const BLOCKSIZE = 16;
/** The S box, 256 8-bit values */
const Sbox = [
0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c, 0x05,
0x2b, 0x67, 0x9a, 0x76, 0x2a, 0xbe, 0x04, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99,
0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef, 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, 0xed, 0xcf, 0xac, 0x62,
0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x08, 0xe8, 0x95, 0x80, 0xdf, 0x94, 0xfa, 0x75, 0x8f, 0x3f, 0xa6,
0x47, 0x07, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c, 0x19, 0xe6, 0x85, 0x4f, 0xa8,
0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb, 0x0f, 0x4b, 0x70, 0x56, 0x9d, 0x35,
0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, 0x87,
0xd4, 0x00, 0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52, 0x4c, 0x36, 0x02, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e,
0xea, 0xbf, 0x8a, 0xd2, 0x40, 0xc7, 0x38, 0xb5, 0xa3, 0xf7, 0xf2, 0xce, 0xf9, 0x61, 0x15, 0xa1,
0xe0, 0xae, 0x5d, 0xa4, 0x9b, 0x34, 0x1a, 0x55, 0xad, 0x93, 0x32, 0x30, 0xf5, 0x8c, 0xb1, 0xe3,
0x1d, 0xf6, 0xe2, 0x2e, 0x82, 0x66, 0xca, 0x60, 0xc0, 0x29, 0x23, 0xab, 0x0d, 0x53, 0x4e, 0x6f,
0xd5, 0xdb, 0x37, 0x45, 0xde, 0xfd, 0x8e, 0x2f, 0x03, 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b, 0x51,
0x8d, 0x1b, 0xaf, 0x92, 0xbb, 0xdd, 0xbc, 0x7f, 0x11, 0xd9, 0x5c, 0x41, 0x1f, 0x10, 0x5a, 0xd8,
0x0a, 0xc1, 0x31, 0x88, 0xa5, 0xcd, 0x7b, 0xbd, 0x2d, 0x74, 0xd0, 0x12, 0xb8, 0xe5, 0xb4, 0xb0,
0x89, 0x69, 0x97, 0x4a, 0x0c, 0x96, 0x77, 0x7e, 0x65, 0xb9, 0xf1, 0x09, 0xc5, 0x6e, 0xc6, 0x84,
0x18, 0xf0, 0x7d, 0xec, 0x3a, 0xdc, 0x4d, 0x20, 0x79, 0xee, 0x5f, 0x3e, 0xd7, 0xcb, 0x39, 0x48
];
/** "Fixed parameter CK" used in key expansion */
const CK = [
0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,
0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,
0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,
0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,
0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279
];
/** "System parameter FK" */
const FK = [0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc];
/**
* Rotating 32-bit shift left
*
* (Note that although JS integers are stored in doubles and thus have 53 bits,
* the JS bitwise operations are 32-bit)
*/
function ROL(i, n) {
return (i << n) | (i >>> (32 - n));
}
/**
* Linear transformation L
*
* @param {integer} b - a 32 bit integer
*/
function transformL(b) {
/* Replace each of the 4 bytes in b with the value at its offset in the Sbox */
b = (Sbox[(b >>> 24) & 0xFF] << 24) | (Sbox[(b >>> 16) & 0xFF] << 16) |
(Sbox[(b >>> 8) & 0xFF] << 8) | Sbox[b & 0xFF];
/* circular rotate and xor */
return b ^ ROL(b, 2) ^ ROL(b, 10) ^ ROL(b, 18) ^ ROL(b, 24);
}
/**
* Linear transformation L'
*
* @param {integer} b - a 32 bit integer
*/
function transformLprime(b) {
/* Replace each of the 4 bytes in b with the value at its offset in the Sbox */
b = (Sbox[(b >>> 24) & 0xFF] << 24) | (Sbox[(b >>> 16) & 0xFF] << 16) |
(Sbox[(b >>> 8) & 0xFF] << 8) | Sbox[b & 0xFF];
return b ^ ROL(b, 13) ^ ROL(b, 23); /* circular rotate and XOR */
}
/**
* Initialize the round key
*/
function initSM4RoundKey(rawkey) {
const K = rawkey.map((a, i) => a ^ FK[i]); /* K = rawkey ^ FK */
const roundKey = [];
for (let i = 0; i < 32; i++)
roundKey[i] = K[i + 4] = K[i] ^ transformLprime(K[i + 1] ^ K[i + 2] ^ K[i + 3] ^ CK[i]);
return roundKey;
}
/**
* Encrypts/decrypts a single block X (4 32-bit values) with a prepared round key.
*
* @param {intArray} X - A cleartext block.
* @param {intArray} roundKey - The round key from initSMRoundKey for encrypting (reversed for decrypting).
* @returns {byteArray} - The cipher text.
*/
function encryptBlockSM4(X, roundKey) {
for (let i = 0; i < NROUNDS; i++)
X[i + 4] = X[i] ^ transformL(X[i + 1] ^ X[i + 2] ^ X[i + 3] ^ roundKey[i]);
return [X[35], X[34], X[33], X[32]];
}
/**
* Takes 16 bytes from an offset in an array and returns an array of 4 32-bit Big-Endian values.
* (DataView won't work portably here as we need Big-Endian)
*
* @param {byteArray} bArray - the array of bytes
* @param {integer} offset - starting offset in the array; 15 bytes must follow it.
*/
function bytesToInts(bArray, offs=0) {
let offset = offs;
const A = (bArray[offset] << 24) | (bArray[offset + 1] << 16) | (bArray[offset + 2] << 8) | bArray[offset + 3];
offset += 4;
const B = (bArray[offset] << 24) | (bArray[offset + 1] << 16) | (bArray[offset + 2] << 8) | bArray[offset + 3];
offset += 4;
const C = (bArray[offset] << 24) | (bArray[offset + 1] << 16) | (bArray[offset + 2] << 8) | bArray[offset + 3];
offset += 4;
const D = (bArray[offset] << 24) | (bArray[offset + 1] << 16) | (bArray[offset + 2] << 8) | bArray[offset + 3];
return [A, B, C, D];
}
/**
* Inverse of bytesToInts above; takes an array of 32-bit integers and turns it into an array of bytes.
* Again, Big-Endian order.
*/
function intsToBytes(ints) {
const bArr = [];
for (let i = 0; i < ints.length; i++) {
bArr.push((ints[i] >> 24) & 0xFF);
bArr.push((ints[i] >> 16) & 0xFF);
bArr.push((ints[i] >> 8) & 0xFF);
bArr.push(ints[i] & 0xFF);
}
return bArr;
}
/**
* Encrypt using SM4 using a given block cipher mode.
*
* @param {byteArray} message - The clear text message; any length under 32 Gb or so.
* @param {byteArray} key - The cipher key, 16 bytes.
* @param {byteArray} iv - The IV or nonce, 16 bytes (not used with ECB mode)
* @param {string} mode - The block cipher mode "CBC", "ECB", "CFB", "OFB", "CTR".
* @param {boolean} noPadding - Don't add PKCS#7 padding if set.
* @returns {byteArray} - The cipher text.
*/
export function encryptSM4(message, key, iv, mode="ECB", noPadding=false) {
const messageLength = message.length;
if (messageLength === 0)
return [];
const roundKey = initSM4RoundKey(bytesToInts(key, 0));
/* Pad with PKCS#7 if requested for ECB/CBC else add zeroes (which are sliced off at the end) */
let padByte = 0;
let nPadding = 16 - (message.length & 0xF);
if (mode === "ECB" || mode === "CBC") {
if (noPadding) {
if (nPadding !== 16)
throw new OperationError(`No padding requested in ${mode} mode but input is not a 16-byte multiple.`);
nPadding = 0;
} else
padByte = nPadding;
}
for (let i = 0; i < nPadding; i++)
message.push(padByte);
const cipherText = [];
switch (mode) {
case "ECB":
for (let i = 0; i < message.length; i += BLOCKSIZE)
Array.prototype.push.apply(cipherText, intsToBytes(encryptBlockSM4(bytesToInts(message, i), roundKey)));
break;
case "CBC":
iv = bytesToInts(iv, 0);
for (let i = 0; i < message.length; i += BLOCKSIZE) {
const block = bytesToInts(message, i);
block[0] ^= iv[0]; block[1] ^= iv[1];
block[2] ^= iv[2]; block[3] ^= iv[3];
iv = encryptBlockSM4(block, roundKey);
Array.prototype.push.apply(cipherText, intsToBytes(iv));
}
break;
case "CFB":
iv = bytesToInts(iv, 0);
for (let i = 0; i < message.length; i += BLOCKSIZE) {
iv = encryptBlockSM4(iv, roundKey);
const block = bytesToInts(message, i);
block[0] ^= iv[0]; block[1] ^= iv[1];
block[2] ^= iv[2]; block[3] ^= iv[3];
Array.prototype.push.apply(cipherText, intsToBytes(block));
iv = block;
}
break;
case "OFB":
iv = bytesToInts(iv, 0);
for (let i = 0; i < message.length; i += BLOCKSIZE) {
iv = encryptBlockSM4(iv, roundKey);
const block = bytesToInts(message, i);
block[0] ^= iv[0]; block[1] ^= iv[1];
block[2] ^= iv[2]; block[3] ^= iv[3];
Array.prototype.push.apply(cipherText, intsToBytes(block));
}
break;
case "CTR":
iv = bytesToInts(iv, 0);
for (let i = 0; i < message.length; i += BLOCKSIZE) {
let iv2 = [...iv]; /* containing the IV + counter */
iv2[3] += (i >> 4);/* Using a 32 bit counter here. 64 Gb encrypts should be enough for everyone. */
iv2 = encryptBlockSM4(iv2, roundKey);
const block = bytesToInts(message, i);
block[0] ^= iv2[0]; block[1] ^= iv2[1];
block[2] ^= iv2[2]; block[3] ^= iv2[3];
Array.prototype.push.apply(cipherText, intsToBytes(block));
}
break;
default:
throw new OperationError("Invalid block cipher mode: "+mode);
}
if (mode !== "ECB" && mode !== "CBC")
return cipherText.slice(0, messageLength);
return cipherText;
}
/**
* Decrypt using SM4 using a given block cipher mode.
*
* @param {byteArray} cipherText - The ciphertext
* @param {byteArray} key - The cipher key, 16 bytes.
* @param {byteArray} iv - The IV or nonce, 16 bytes (not used with ECB mode)
* @param {string} mode - The block cipher mode "CBC", "ECB", "CFB", "OFB", "CTR"
* @param {boolean] ignorePadding - If true, ignore padding issues in ECB/CBC mode.
* @returns {byteArray} - The cipher text.
*/
export function decryptSM4(cipherText, key, iv, mode="ECB", ignorePadding=false) {
const originalLength = cipherText.length;
if (originalLength === 0)
return [];
let roundKey = initSM4RoundKey(bytesToInts(key, 0));
if (mode === "ECB" || mode === "CBC") {
/* Init decryption key */
roundKey = roundKey.reverse();
if ((originalLength & 0xF) !== 0 && !ignorePadding)
throw new OperationError(`With ECB or CBC modes, the input must be divisible into 16 byte blocks. (${cipherText.length & 0xF} bytes extra)`);
} else { /* Pad dummy bytes for other modes, chop them off at the end */
while ((cipherText.length & 0xF) !== 0)
cipherText.push(0);
}
const clearText = [];
switch (mode) {
case "ECB":
for (let i = 0; i < cipherText.length; i += BLOCKSIZE)
Array.prototype.push.apply(clearText, intsToBytes(encryptBlockSM4(bytesToInts(cipherText, i), roundKey)));
break;
case "CBC":
iv = bytesToInts(iv, 0);
for (let i = 0; i < cipherText.length; i += BLOCKSIZE) {
const block = encryptBlockSM4(bytesToInts(cipherText, i), roundKey);
block[0] ^= iv[0]; block[1] ^= iv[1];
block[2] ^= iv[2]; block[3] ^= iv[3];
Array.prototype.push.apply(clearText, intsToBytes(block));
iv = bytesToInts(cipherText, i);
}
break;
case "CFB":
iv = bytesToInts(iv, 0);
for (let i = 0; i < cipherText.length; i += BLOCKSIZE) {
iv = encryptBlockSM4(iv, roundKey);
const block = bytesToInts(cipherText, i);
block[0] ^= iv[0]; block[1] ^= iv[1];
block[2] ^= iv[2]; block[3] ^= iv[3];
Array.prototype.push.apply(clearText, intsToBytes(block));
iv = bytesToInts(cipherText, i);
}
break;
case "OFB":
iv = bytesToInts(iv, 0);
for (let i = 0; i < cipherText.length; i += BLOCKSIZE) {
iv = encryptBlockSM4(iv, roundKey);
const block = bytesToInts(cipherText, i);
block[0] ^= iv[0]; block[1] ^= iv[1];
block[2] ^= iv[2]; block[3] ^= iv[3];
Array.prototype.push.apply(clearText, intsToBytes(block));
}
break;
case "CTR":
iv = bytesToInts(iv, 0);
for (let i = 0; i < cipherText.length; i += BLOCKSIZE) {
let iv2 = [...iv]; /* containing the IV + counter */
iv2[3] += (i >> 4);/* Using a 32 bit counter here. 64 Gb encrypts should be enough for everyone. */
iv2 = encryptBlockSM4(iv2, roundKey);
const block = bytesToInts(cipherText, i);
block[0] ^= iv2[0]; block[1] ^= iv2[1];
block[2] ^= iv2[2]; block[3] ^= iv2[3];
Array.prototype.push.apply(clearText, intsToBytes(block));
}
break;
default:
throw new OperationError(`Invalid block cipher mode: ${mode}`);
}
/* Check PKCS#7 padding */
if (mode === "ECB" || mode === "CBC") {
if (ignorePadding)
return clearText;
const padByte = clearText[clearText.length - 1];
if (padByte > 16)
throw new OperationError("Invalid PKCS#7 padding.");
for (let i = 0; i < padByte; i++)
if (clearText[clearText.length -i - 1] !== padByte)
throw new OperationError("Invalid PKCS#7 padding.");
return clearText.slice(0, clearText.length - padByte);
}
return clearText.slice(0, originalLength);
}

105
src/core/lib/Sort.mjs Normal file
View File

@@ -0,0 +1,105 @@
/**
* Sorting functions
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2022
* @license Apache-2.0
*
*/
/**
* Comparison operation for sorting of strings ignoring case.
*
* @param {string} a
* @param {string} b
* @returns {number}
*/
export function caseInsensitiveSort(a, b) {
return a.toLowerCase().localeCompare(b.toLowerCase());
}
/**
* Comparison operation for sorting of IPv4 addresses.
*
* @param {string} a
* @param {string} b
* @returns {number}
*/
export function ipSort(a, b) {
let a_ = a.split("."),
b_ = b.split(".");
a_ = a_[0] * 0x1000000 + a_[1] * 0x10000 + a_[2] * 0x100 + a_[3] * 1;
b_ = b_[0] * 0x1000000 + b_[1] * 0x10000 + b_[2] * 0x100 + b_[3] * 1;
if (isNaN(a_) && !isNaN(b_)) return 1;
if (!isNaN(a_) && isNaN(b_)) return -1;
if (isNaN(a_) && isNaN(b_)) return a.localeCompare(b);
return a_ - b_;
}
/**
* Comparison operation for sorting of numeric values.
*
* @author Chris van Marle
* @param {string} a
* @param {string} b
* @returns {number}
*/
export function numericSort(a, b) {
const a_ = a.split(/([^\d]+)/),
b_ = b.split(/([^\d]+)/);
for (let i = 0; i < a_.length && i < b.length; ++i) {
if (isNaN(a_[i]) && !isNaN(b_[i])) return 1; // Numbers after non-numbers
if (!isNaN(a_[i]) && isNaN(b_[i])) return -1;
if (isNaN(a_[i]) && isNaN(b_[i])) {
const ret = a_[i].localeCompare(b_[i]); // Compare strings
if (ret !== 0) return ret;
}
if (!isNaN(a_[i]) && !isNaN(b_[i])) { // Compare numbers
if (a_[i] - b_[i] !== 0) return a_[i] - b_[i];
}
}
return a.localeCompare(b);
}
/**
* Comparison operation for sorting of hexadecimal values.
*
* @author Chris van Marle
* @param {string} a
* @param {string} b
* @returns {number}
*/
export function hexadecimalSort(a, b) {
let a_ = a.split(/([^\da-f]+)/i),
b_ = b.split(/([^\da-f]+)/i);
a_ = a_.map(v => {
const t = parseInt(v, 16);
return isNaN(t) ? v : t;
});
b_ = b_.map(v => {
const t = parseInt(v, 16);
return isNaN(t) ? v : t;
});
for (let i = 0; i < a_.length && i < b.length; ++i) {
if (isNaN(a_[i]) && !isNaN(b_[i])) return 1; // Numbers after non-numbers
if (!isNaN(a_[i]) && isNaN(b_[i])) return -1;
if (isNaN(a_[i]) && isNaN(b_[i])) {
const ret = a_[i].localeCompare(b_[i]); // Compare strings
if (ret !== 0) return ret;
}
if (!isNaN(a_[i]) && !isNaN(b_[i])) { // Compare numbers
if (a_[i] - b_[i] !== 0) return a_[i] - b_[i];
}
}
return a.localeCompare(b);
}

View File

@@ -27,15 +27,17 @@ export default class Stream {
}
/**
* Get a number of bytes from the current position.
* Get a number of bytes from the current position, or all remaining bytes.
*
* @param {number} numBytes
* @param {number} [numBytes=null]
* @returns {Uint8Array}
*/
getBytes(numBytes) {
getBytes(numBytes=null) {
if (this.position > this.length) return undefined;
const newPosition = this.position + numBytes;
const newPosition = numBytes !== null ?
this.position + numBytes :
this.length;
const bytes = this.bytes.slice(this.position, newPosition);
this.position = newPosition;
this.bitPos = 0;
@@ -91,34 +93,40 @@ export default class Stream {
}
/**
* Reads a number of bits from the buffer.
*
* @TODO Add endianness
* Reads a number of bits from the buffer in big or little endian.
*
* @param {number} numBits
* @param {string} [endianness="be"]
* @returns {number}
*/
readBits(numBits) {
readBits(numBits, endianness="be") {
if (this.position > this.length) return undefined;
let bitBuf = 0,
bitBufLen = 0;
// Add remaining bits from current byte
bitBuf = (this.bytes[this.position++] & bitMask(this.bitPos)) >>> this.bitPos;
bitBuf = this.bytes[this.position++] & bitMask(this.bitPos);
if (endianness !== "be") bitBuf >>>= this.bitPos;
bitBufLen = 8 - this.bitPos;
this.bitPos = 0;
// Not enough bits yet
while (bitBufLen < numBits) {
bitBuf |= this.bytes[this.position++] << bitBufLen;
if (endianness === "be")
bitBuf = (bitBuf << bitBufLen) | this.bytes[this.position++];
else
bitBuf |= this.bytes[this.position++] << bitBufLen;
bitBufLen += 8;
}
// Reverse back to numBits
if (bitBufLen > numBits) {
const excess = bitBufLen - numBits;
bitBuf &= (1 << numBits) - 1;
if (endianness === "be")
bitBuf >>>= excess;
else
bitBuf &= (1 << numBits) - 1;
bitBufLen -= excess;
this.position--;
this.bitPos = 8 - excess;
@@ -133,7 +141,9 @@ export default class Stream {
* @returns {number} The bit mask
*/
function bitMask(bitPos) {
return 256 - (1 << bitPos);
return endianness === "be" ?
(1 << (8 - bitPos)) - 1 :
256 - (1 << bitPos);
}
}
@@ -177,7 +187,7 @@ export default class Stream {
// Get the skip table.
const skiptable = preprocess(val, length);
let found = true;
let found;
while (this.position < this.length) {
// Until we hit the final element of val in the stream.

View File

@@ -22,7 +22,7 @@ class AESDecrypt extends Operation {
this.name = "AES Decrypt";
this.module = "Ciphers";
this.description = "Advanced Encryption Standard (AES) is a U.S. Federal Information Processing Standard (FIPS). It was selected after a 5-year process where 15 competing designs were evaluated.<br><br><b>Key:</b> The following algorithms will be used based on the size of the key:<ul><li>16 bytes = AES-128</li><li>24 bytes = AES-192</li><li>32 bytes = AES-256</li></ul><br><br><b>IV:</b> The Initialization Vector should be 16 bytes long. If not entered, it will default to 16 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.<br><br><b>GCM Tag:</b> This field is ignored unless 'GCM' mode is used.";
this.description = "Advanced Encryption Standard (AES) is a U.S. Federal Information Processing Standard (FIPS). It was selected after a 5-year process where 15 competing designs were evaluated.<br><br><b>Key:</b> The following algorithms will be used based on the size of the key:<ul><li>16 bytes = AES-128</li><li>24 bytes = AES-192</li><li>32 bytes = AES-256</li></ul><br><br><b>IV:</b> The Initialization Vector should be 16 bytes long. If not entered, it will default to 16 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used as a default.<br><br><b>GCM Tag:</b> This field is ignored unless 'GCM' mode is used.";
this.infoURL = "https://wikipedia.org/wiki/Advanced_Encryption_Standard";
this.inputType = "string";
this.outputType = "string";
@@ -41,8 +41,41 @@ class AESDecrypt extends Operation {
},
{
"name": "Mode",
"type": "option",
"value": ["CBC", "CFB", "OFB", "CTR", "GCM", "ECB"]
"type": "argSelector",
"value": [
{
name: "CBC",
off: [5, 6]
},
{
name: "CFB",
off: [5, 6]
},
{
name: "OFB",
off: [5, 6]
},
{
name: "CTR",
off: [5, 6]
},
{
name: "GCM",
on: [5, 6]
},
{
name: "ECB",
off: [5, 6]
},
{
name: "CBC/NoPadding",
off: [5, 6]
},
{
name: "ECB/NoPadding",
off: [5, 6]
}
]
},
{
"name": "Input",
@@ -59,6 +92,12 @@ class AESDecrypt extends Operation {
"type": "toggleString",
"value": "",
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
},
{
"name": "Additional Authenticated Data",
"type": "toggleString",
"value": "",
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
}
];
}
@@ -73,10 +112,12 @@ class AESDecrypt extends Operation {
run(input, args) {
const key = Utils.convertToByteString(args[0].string, args[0].option),
iv = Utils.convertToByteString(args[1].string, args[1].option),
mode = args[2],
mode = args[2].substring(0, 3),
noPadding = args[2].endsWith("NoPadding"),
inputType = args[3],
outputType = args[4],
gcmTag = Utils.convertToByteString(args[5].string, args[5].option);
gcmTag = Utils.convertToByteString(args[5].string, args[5].option),
aad = Utils.convertToByteString(args[6].string, args[6].option);
if ([16, 24, 32].indexOf(key.length) < 0) {
throw new OperationError(`Invalid key length: ${key.length} bytes
@@ -90,9 +131,18 @@ The following algorithms will be used based on the size of the key:
input = Utils.convertToByteString(input, inputType);
const decipher = forge.cipher.createDecipher("AES-" + mode, key);
/* Allow for a "no padding" mode */
if (noPadding) {
decipher.mode.unpad = function(output, options) {
return true;
};
}
decipher.start({
iv: iv.length === 0 ? "" : iv,
tag: gcmTag
tag: mode === "GCM" ? gcmTag : undefined,
additionalData: mode === "GCM" ? aad : undefined
});
decipher.update(forge.util.createBuffer(input));
const result = decipher.finish();

View File

@@ -41,8 +41,33 @@ class AESEncrypt extends Operation {
},
{
"name": "Mode",
"type": "option",
"value": ["CBC", "CFB", "OFB", "CTR", "GCM", "ECB"]
"type": "argSelector",
"value": [
{
name: "CBC",
off: [5]
},
{
name: "CFB",
off: [5]
},
{
name: "OFB",
off: [5]
},
{
name: "CTR",
off: [5]
},
{
name: "GCM",
on: [5]
},
{
name: "ECB",
off: [5]
}
]
},
{
"name": "Input",
@@ -53,6 +78,12 @@ class AESEncrypt extends Operation {
"name": "Output",
"type": "option",
"value": ["Hex", "Raw"]
},
{
"name": "Additional Authenticated Data",
"type": "toggleString",
"value": "",
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
}
];
}
@@ -69,7 +100,8 @@ class AESEncrypt extends Operation {
iv = Utils.convertToByteString(args[1].string, args[1].option),
mode = args[2],
inputType = args[3],
outputType = args[4];
outputType = args[4],
aad = Utils.convertToByteString(args[5].string, args[5].option);
if ([16, 24, 32].indexOf(key.length) < 0) {
throw new OperationError(`Invalid key length: ${key.length} bytes
@@ -83,7 +115,10 @@ The following algorithms will be used based on the size of the key:
input = Utils.convertToByteString(input, inputType);
const cipher = forge.cipher.createCipher("AES-" + mode, key);
cipher.start({iv: iv});
cipher.start({
iv: iv,
additionalData: mode === "GCM" ? aad : undefined
});
cipher.update(forge.util.createBuffer(input));
cipher.finish();

View File

@@ -1,6 +1,9 @@
/**
* Emulation of the Bombe machine.
*
* Tested against the Bombe Rebuild at Bletchley Park's TNMOC
* using a variety of inputs and settings to confirm correctness.
*
* @author s2224834
* @copyright Crown Copyright 2019
* @license Apache-2.0

View File

@@ -0,0 +1,41 @@
/**
* @author Danh4 [dan.h4@ncsc.gov.uk]
* @copyright Crown Copyright 2020
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import Cbor from "cbor";
/**
* CBOR Decode operation
*/
class CBORDecode extends Operation {
/**
* CBORDecode constructor
*/
constructor() {
super();
this.name = "CBOR Decode";
this.module = "Serialise";
this.description = "Concise Binary Object Representation (CBOR) is a binary data serialization format loosely based on JSON. Like JSON it allows the transmission of data objects that contain namevalue pairs, but in a more concise manner. This increases processing and transfer speeds at the cost of human readability. It is defined in IETF RFC 8949.";
this.infoURL = "https://wikipedia.org/wiki/CBOR";
this.inputType = "ArrayBuffer";
this.outputType = "JSON";
this.args = [];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {JSON}
*/
run(input, args) {
return Cbor.decodeFirstSync(Buffer.from(input).toString("hex"));
}
}
export default CBORDecode;

View File

@@ -0,0 +1,41 @@
/**
* @author Danh4 [dan.h4@ncsc.gov.uk]
* @copyright Crown Copyright 2020
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import Cbor from "cbor";
/**
* CBOR Encode operation
*/
class CBOREncode extends Operation {
/**
* CBOREncode constructor
*/
constructor() {
super();
this.name = "CBOR Encode";
this.module = "Serialise";
this.description = "Concise Binary Object Representation (CBOR) is a binary data serialization format loosely based on JSON. Like JSON it allows the transmission of data objects that contain namevalue pairs, but in a more concise manner. This increases processing and transfer speeds at the cost of human readability. It is defined in IETF RFC 8949.";
this.infoURL = "https://wikipedia.org/wiki/CBOR";
this.inputType = "JSON";
this.outputType = "ArrayBuffer";
this.args = [];
}
/**
* @param {JSON} input
* @param {Object[]} args
* @returns {ArrayBuffer}
*/
run(input, args) {
return new Uint8Array(Cbor.encodeCanonical(input)).buffer;
}
}
export default CBOREncode;

View File

@@ -1,6 +1,9 @@
/**
* Emulation of Colossus.
*
* Tested against the Colossus Rebuild at Bletchley Park's TNMOC
* using a variety of inputs and settings to confirm correctness.
*
* @author VirtualColossus [martin@virtualcolossus.co.uk]
* @copyright Crown Copyright 2019
* @license Apache-2.0
@@ -125,7 +128,8 @@ class Colossus extends Operation {
},
{
name: "R1-Negate",
type: "boolean"
type: "boolean",
value: false
},
{
name: "R1-Counter",
@@ -164,7 +168,8 @@ class Colossus extends Operation {
},
{
name: "R2-Negate",
type: "boolean"
type: "boolean",
value: false
},
{
name: "R2-Counter",
@@ -203,7 +208,8 @@ class Colossus extends Operation {
},
{
name: "R3-Negate",
type: "boolean"
type: "boolean",
value: false
},
{
name: "R3-Counter",
@@ -212,7 +218,8 @@ class Colossus extends Operation {
},
{
name: "Negate All",
type: "boolean"
type: "boolean",
value: false
},
{
name: "K Rack: Addition",
@@ -220,23 +227,28 @@ class Colossus extends Operation {
},
{
name: "Add-Q1",
type: "boolean"
type: "boolean",
value: false
},
{
name: "Add-Q2",
type: "boolean"
type: "boolean",
value: false
},
{
name: "Add-Q3",
type: "boolean"
type: "boolean",
value: false
},
{
name: "Add-Q4",
type: "boolean"
type: "boolean",
value: false
},
{
name: "Add-Q5",
type: "boolean"
type: "boolean",
value: false
},
{
name: "Add-Equals",
@@ -246,11 +258,13 @@ class Colossus extends Operation {
},
{
name: "Add-Counter1",
type: "boolean"
type: "boolean",
value: false
},
{
name: "Add Negate All",
type: "boolean"
type: "boolean",
value: false
},
{
name: "Total Motor",

View File

@@ -22,7 +22,7 @@ class DESDecrypt extends Operation {
this.name = "DES Decrypt";
this.module = "Ciphers";
this.description = "DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.<br><br><b>Key:</b> DES uses a key length of 8 bytes (64 bits).<br>Triple DES uses a key length of 24 bytes (192 bits).<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.";
this.description = "DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.<br><br><b>Key:</b> DES uses a key length of 8 bytes (64 bits).<br>Triple DES uses a key length of 24 bytes (192 bits).<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used as a default.";
this.infoURL = "https://wikipedia.org/wiki/Data_Encryption_Standard";
this.inputType = "string";
this.outputType = "string";
@@ -42,7 +42,7 @@ class DESDecrypt extends Operation {
{
"name": "Mode",
"type": "option",
"value": ["CBC", "CFB", "OFB", "CTR", "ECB"]
"value": ["CBC", "CFB", "OFB", "CTR", "ECB", "CBC/NoPadding", "ECB/NoPadding"]
},
{
"name": "Input",
@@ -65,7 +65,9 @@ class DESDecrypt extends Operation {
run(input, args) {
const key = Utils.convertToByteString(args[0].string, args[0].option),
iv = Utils.convertToByteArray(args[1].string, args[1].option),
[,, mode, inputType, outputType] = args;
mode = args[2].substring(0, 3),
noPadding = args[2].endsWith("NoPadding"),
[,,, inputType, outputType] = args;
if (key.length !== 8) {
throw new OperationError(`Invalid key length: ${key.length} bytes
@@ -83,6 +85,14 @@ Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
input = Utils.convertToByteString(input, inputType);
const decipher = forge.cipher.createDecipher("DES-" + mode, key);
/* Allow for a "no padding" mode */
if (noPadding) {
decipher.mode.unpad = function(output, options) {
return true;
};
}
decipher.start({iv: iv});
decipher.update(forge.util.createBuffer(input));
const result = decipher.finish();

View File

@@ -67,7 +67,7 @@ class DeriveEVPKey extends Operation {
iterations = args[2],
hasher = args[3],
salt = Utils.convertToByteString(args[4].string, args[4].option),
key = CryptoJS.EvpKDF(passphrase, salt, {
key = CryptoJS.EvpKDF(passphrase, salt, { // lgtm [js/insufficient-password-hash]
keySize: keySize,
hasher: CryptoJS.algo[hasher],
iterations: iterations,

View File

@@ -1,6 +1,9 @@
/**
* Emulation of the Enigma machine.
*
* Tested against various genuine Enigma machines using a variety of inputs
* and settings to confirm correctness.
*
* @author s2224834
* @copyright Crown Copyright 2019
* @license Apache-2.0

View File

@@ -44,7 +44,13 @@ class ExtractDates extends Operation {
date3 = "(?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])[- /.](?:19|20)\\d\\d", // mm/dd/yyyy
regex = new RegExp(date1 + "|" + date2 + "|" + date3, "ig");
return search(input, regex, null, displayTotal);
const results = search(input, regex);
if (displayTotal) {
return `Total found: ${results.length}\n\n${results.join("\n")}`;
} else {
return results.join("\n");
}
}
}

View File

@@ -6,6 +6,7 @@
import Operation from "../Operation.mjs";
import { search, DOMAIN_REGEX } from "../lib/Extract.mjs";
import { caseInsensitiveSort } from "../lib/Sort.mjs";
/**
* Extract domains operation
@@ -25,9 +26,19 @@ class ExtractDomains extends Operation {
this.outputType = "string";
this.args = [
{
"name": "Display total",
"type": "boolean",
"value": "Extract.DISPLAY_TOTAL"
name: "Display total",
type: "boolean",
value: false
},
{
name: "Sort",
type: "boolean",
value: false
},
{
name: "Unique",
type: "boolean",
value: false
}
];
}
@@ -38,8 +49,21 @@ class ExtractDomains extends Operation {
* @returns {string}
*/
run(input, args) {
const displayTotal = args[0];
return search(input, DOMAIN_REGEX, null, displayTotal);
const [displayTotal, sort, unique] = args;
const results = search(
input,
DOMAIN_REGEX,
null,
sort ? caseInsensitiveSort : null,
unique
);
if (displayTotal) {
return `Total found: ${results.length}\n\n${results.join("\n")}`;
} else {
return results.join("\n");
}
}
}

View File

@@ -6,6 +6,7 @@
import Operation from "../Operation.mjs";
import { search } from "../lib/Extract.mjs";
import { caseInsensitiveSort } from "../lib/Sort.mjs";
/**
* Extract email addresses operation
@@ -25,9 +26,19 @@ class ExtractEmailAddresses extends Operation {
this.outputType = "string";
this.args = [
{
"name": "Display total",
"type": "boolean",
"value": false
name: "Display total",
type: "boolean",
value: false
},
{
name: "Sort",
type: "boolean",
value: false
},
{
name: "Unique",
type: "boolean",
value: false
}
];
}
@@ -38,10 +49,23 @@ class ExtractEmailAddresses extends Operation {
* @returns {string}
*/
run(input, args) {
const displayTotal = args[0],
// email regex from: https://www.regextester.com/98066
regex = /(?:[\u00A0-\uD7FF\uE000-\uFFFF-a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[\u00A0-\uD7FF\uE000-\uFFFF-a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[\u00A0-\uD7FF\uE000-\uFFFF-a-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFF-a-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFF-a-z0-9])?\.)+[\u00A0-\uD7FF\uE000-\uFFFF-a-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFF-a-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFF-a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/ig;
return search(input, regex, null, displayTotal);
const [displayTotal, sort, unique] = args,
// email regex from: https://www.regextester.com/98066
regex = /(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?\.)+[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}\])/ig;
const results = search(
input,
regex,
null,
sort ? caseInsensitiveSort : null,
unique
);
if (displayTotal) {
return `Total found: ${results.length}\n\n${results.join("\n")}`;
} else {
return results.join("\n");
}
}
}

View File

@@ -6,6 +6,7 @@
import Operation from "../Operation.mjs";
import { search } from "../lib/Extract.mjs";
import { caseInsensitiveSort } from "../lib/Sort.mjs";
/**
* Extract file paths operation
@@ -25,19 +26,29 @@ class ExtractFilePaths extends Operation {
this.outputType = "string";
this.args = [
{
"name": "Windows",
"type": "boolean",
"value": true
name: "Windows",
type: "boolean",
value: true
},
{
"name": "UNIX",
"type": "boolean",
"value": true
name: "UNIX",
type: "boolean",
value: true
},
{
"name": "Display total",
"type": "boolean",
"value": false
name: "Display total",
type: "boolean",
value: false
},
{
name: "Sort",
type: "boolean",
value: false
},
{
name: "Unique",
type: "boolean",
value: false
}
];
}
@@ -48,7 +59,7 @@ class ExtractFilePaths extends Operation {
* @returns {string}
*/
run(input, args) {
const [includeWinPath, includeUnixPath, displayTotal] = args,
const [includeWinPath, includeUnixPath, displayTotal, sort, unique] = args,
winDrive = "[A-Z]:\\\\",
winName = "[A-Z\\d][A-Z\\d\\- '_\\(\\)~]{0,61}",
winExt = "[A-Z\\d]{1,6}",
@@ -65,12 +76,25 @@ class ExtractFilePaths extends Operation {
filePaths = unixPath;
}
if (filePaths) {
const regex = new RegExp(filePaths, "ig");
return search(input, regex, null, displayTotal);
} else {
if (!filePaths) {
return "";
}
const regex = new RegExp(filePaths, "ig");
const results = search(
input,
regex,
null,
sort ? caseInsensitiveSort : null,
unique
);
if (displayTotal) {
return `Total found: ${results.length}\n\n${results.join("\n")}`;
} else {
return results.join("\n");
}
}
}

View File

@@ -21,9 +21,24 @@ class ExtractFiles extends Operation {
constructor() {
super();
// Get the first extension for each signature that can be extracted
let supportedExts = Object.keys(FILE_SIGNATURES).map(cat => {
return FILE_SIGNATURES[cat]
.filter(sig => sig.extractor)
.map(sig => sig.extension.toUpperCase());
});
// Flatten categories and remove duplicates
supportedExts = [].concat(...supportedExts).unique();
this.name = "Extract Files";
this.module = "Default";
this.description = "Performs file carving to attempt to extract files from the input.<br><br>This operation is currently capable of carving out the following formats:<ul><li>JPG</li><li>EXE</li><li>ZIP</li><li>PDF</li><li>PNG</li><li>BMP</li><li>FLV</li><li>RTF</li><li>DOCX, PPTX, XLSX</li><li>EPUB</li><li>GZIP</li><li>ZLIB</li><li>ELF, BIN, AXF, O, PRX, SO</li></ul>";
this.description = `Performs file carving to attempt to extract files from the input.<br><br>This operation is currently capable of carving out the following formats:
<ul>
<li>
${supportedExts.join("</li><li>")}
</li>
</ul>`;
this.infoURL = "https://forensicswiki.xyz/wiki/index.php?title=File_Carving";
this.inputType = "ArrayBuffer";
this.outputType = "List<File>";
@@ -38,7 +53,7 @@ class ExtractFiles extends Operation {
{
name: "Ignore failed extractions",
type: "boolean",
value: "true"
value: true
}
]);
}

View File

@@ -0,0 +1,324 @@
/**
* @author n1073645 [n1073645@gmail.com]
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2020
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import Utils from "../Utils.mjs";
/**
* Extract ID3 operation
*/
class ExtractID3 extends Operation {
/**
* ExtractID3 constructor
*/
constructor() {
super();
this.name = "Extract ID3";
this.module = "Default";
this.description = "This operation extracts ID3 metadata from an MP3 file.<br><br>ID3 is a metadata container most often used in conjunction with the MP3 audio file format. It allows information such as the title, artist, album, track number, and other information about the file to be stored in the file itself.";
this.infoURL = "https://wikipedia.org/wiki/ID3";
this.inputType = "ArrayBuffer";
this.outputType = "JSON";
this.presentType = "html";
this.args = [];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {JSON}
*/
run(input, args) {
input = new Uint8Array(input);
/**
* Extracts the ID3 header fields.
*/
function extractHeader() {
if (!Array.from(input.slice(0, 3)).equals([0x49, 0x44, 0x33]))
throw new OperationError("No valid ID3 header.");
const header = {
"Type": "ID3",
// Tag version
"Version": input[3].toString() + "." + input[4].toString(),
// Header version
"Flags": input[5].toString()
};
input = input.slice(6);
return header;
}
/**
* Converts the size fields to a single integer.
*
* @param {number} num
* @returns {string}
*/
function readSize(num) {
let result = 0;
// The sizes are 7 bit numbers stored in 8 bit locations
for (let i = (num) * 7; i; i -= 7) {
result = (result << i) | input[0];
input = input.slice(1);
}
return result;
}
/**
* Reads frame header based on ID.
*
* @param {string} id
* @returns {number}
*/
function readFrame(id) {
const frame = {};
// Size of frame
const size = readSize(4);
frame.Size = size.toString();
frame.Description = FRAME_DESCRIPTIONS[id];
input = input.slice(2);
// Read data from frame
let data = "";
for (let i = 1; i < size; i++)
data += String.fromCharCode(input[i]);
frame.Data = data;
// Move to next Frame
input = input.slice(size);
return [frame, size];
}
const result = extractHeader();
const headerTagSize = readSize(4);
result.Size = headerTagSize.toString();
const tags = {};
let pos = 10;
// While the current element is in the header
while (pos < headerTagSize) {
// Frame Identifier of frame
let id = String.fromCharCode(input[0]) + String.fromCharCode(input[1]) + String.fromCharCode(input[2]);
input = input.slice(3);
// If the next character is non-zero it is an identifier
if (input[0] !== 0) {
id += String.fromCharCode(input[0]);
}
input = input.slice(1);
if (id in FRAME_DESCRIPTIONS) {
const [frame, size] = readFrame(id);
tags[id] = frame;
pos += 10 + size;
} else if (id === "\x00\x00\x00") { // end of header
break;
} else {
throw new OperationError("Unknown Frame Identifier: " + id);
}
}
result.Tags = tags;
return result;
}
/**
* Displays the extracted data in a more accessible format for web apps.
* @param {JSON} data
* @returns {html}
*/
present(data) {
if (!data || !Object.prototype.hasOwnProperty.call(data, "Tags"))
return JSON.stringify(data, null, 4);
let output = `<table class="table table-hover table-sm table-bordered table-nonfluid">
<tr><th>Tag</th><th>Description</th><th>Data</th></tr>`;
for (const tagID in data.Tags) {
const description = data.Tags[tagID].Description,
contents = data.Tags[tagID].Data;
output += `<tr><td>${tagID}</td><td>${Utils.escapeHtml(description)}</td><td>${Utils.escapeHtml(contents)}</td></tr>`;
}
output += "</table>";
return output;
}
}
// Borrowed from https://github.com/aadsm/jsmediatags
const FRAME_DESCRIPTIONS = {
// v2.2
"BUF": "Recommended buffer size",
"CNT": "Play counter",
"COM": "Comments",
"CRA": "Audio encryption",
"CRM": "Encrypted meta frame",
"ETC": "Event timing codes",
"EQU": "Equalization",
"GEO": "General encapsulated object",
"IPL": "Involved people list",
"LNK": "Linked information",
"MCI": "Music CD Identifier",
"MLL": "MPEG location lookup table",
"PIC": "Attached picture",
"POP": "Popularimeter",
"REV": "Reverb",
"RVA": "Relative volume adjustment",
"SLT": "Synchronized lyric/text",
"STC": "Synced tempo codes",
"TAL": "Album/Movie/Show title",
"TBP": "BPM (Beats Per Minute)",
"TCM": "Composer",
"TCO": "Content type",
"TCR": "Copyright message",
"TDA": "Date",
"TDY": "Playlist delay",
"TEN": "Encoded by",
"TFT": "File type",
"TIM": "Time",
"TKE": "Initial key",
"TLA": "Language(s)",
"TLE": "Length",
"TMT": "Media type",
"TOA": "Original artist(s)/performer(s)",
"TOF": "Original filename",
"TOL": "Original Lyricist(s)/text writer(s)",
"TOR": "Original release year",
"TOT": "Original album/Movie/Show title",
"TP1": "Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group",
"TP2": "Band/Orchestra/Accompaniment",
"TP3": "Conductor/Performer refinement",
"TP4": "Interpreted, remixed, or otherwise modified by",
"TPA": "Part of a set",
"TPB": "Publisher",
"TRC": "ISRC (International Standard Recording Code)",
"TRD": "Recording dates",
"TRK": "Track number/Position in set",
"TSI": "Size",
"TSS": "Software/hardware and settings used for encoding",
"TT1": "Content group description",
"TT2": "Title/Songname/Content description",
"TT3": "Subtitle/Description refinement",
"TXT": "Lyricist/text writer",
"TXX": "User defined text information frame",
"TYE": "Year",
"UFI": "Unique file identifier",
"ULT": "Unsychronized lyric/text transcription",
"WAF": "Official audio file webpage",
"WAR": "Official artist/performer webpage",
"WAS": "Official audio source webpage",
"WCM": "Commercial information",
"WCP": "Copyright/Legal information",
"WPB": "Publishers official webpage",
"WXX": "User defined URL link frame",
// v2.3
"AENC": "Audio encryption",
"APIC": "Attached picture",
"ASPI": "Audio seek point index",
"CHAP": "Chapter",
"CTOC": "Table of contents",
"COMM": "Comments",
"COMR": "Commercial frame",
"ENCR": "Encryption method registration",
"EQU2": "Equalisation (2)",
"EQUA": "Equalization",
"ETCO": "Event timing codes",
"GEOB": "General encapsulated object",
"GRID": "Group identification registration",
"IPLS": "Involved people list",
"LINK": "Linked information",
"MCDI": "Music CD identifier",
"MLLT": "MPEG location lookup table",
"OWNE": "Ownership frame",
"PRIV": "Private frame",
"PCNT": "Play counter",
"POPM": "Popularimeter",
"POSS": "Position synchronisation frame",
"RBUF": "Recommended buffer size",
"RVA2": "Relative volume adjustment (2)",
"RVAD": "Relative volume adjustment",
"RVRB": "Reverb",
"SEEK": "Seek frame",
"SYLT": "Synchronized lyric/text",
"SYTC": "Synchronized tempo codes",
"TALB": "Album/Movie/Show title",
"TBPM": "BPM (beats per minute)",
"TCOM": "Composer",
"TCON": "Content type",
"TCOP": "Copyright message",
"TDAT": "Date",
"TDLY": "Playlist delay",
"TDRC": "Recording time",
"TDRL": "Release time",
"TDTG": "Tagging time",
"TENC": "Encoded by",
"TEXT": "Lyricist/Text writer",
"TFLT": "File type",
"TIME": "Time",
"TIPL": "Involved people list",
"TIT1": "Content group description",
"TIT2": "Title/songname/content description",
"TIT3": "Subtitle/Description refinement",
"TKEY": "Initial key",
"TLAN": "Language(s)",
"TLEN": "Length",
"TMCL": "Musician credits list",
"TMED": "Media type",
"TMOO": "Mood",
"TOAL": "Original album/movie/show title",
"TOFN": "Original filename",
"TOLY": "Original lyricist(s)/text writer(s)",
"TOPE": "Original artist(s)/performer(s)",
"TORY": "Original release year",
"TOWN": "File owner/licensee",
"TPE1": "Lead performer(s)/Soloist(s)",
"TPE2": "Band/orchestra/accompaniment",
"TPE3": "Conductor/performer refinement",
"TPE4": "Interpreted, remixed, or otherwise modified by",
"TPOS": "Part of a set",
"TPRO": "Produced notice",
"TPUB": "Publisher",
"TRCK": "Track number/Position in set",
"TRDA": "Recording dates",
"TRSN": "Internet radio station name",
"TRSO": "Internet radio station owner",
"TSOA": "Album sort order",
"TSOP": "Performer sort order",
"TSOT": "Title sort order",
"TSIZ": "Size",
"TSRC": "ISRC (international standard recording code)",
"TSSE": "Software/Hardware and settings used for encoding",
"TSST": "Set subtitle",
"TYER": "Year",
"TXXX": "User defined text information frame",
"UFID": "Unique file identifier",
"USER": "Terms of use",
"USLT": "Unsychronized lyric/text transcription",
"WCOM": "Commercial information",
"WCOP": "Copyright/Legal information",
"WOAF": "Official audio file webpage",
"WOAR": "Official artist/performer webpage",
"WOAS": "Official audio source webpage",
"WORS": "Official internet radio station homepage",
"WPAY": "Payment",
"WPUB": "Publishers official webpage",
"WXXX": "User defined URL link frame"
};
export default ExtractID3;

View File

@@ -6,6 +6,7 @@
import Operation from "../Operation.mjs";
import { search } from "../lib/Extract.mjs";
import { ipSort } from "../lib/Sort.mjs";
/**
* Extract IP addresses operation
@@ -25,24 +26,34 @@ class ExtractIPAddresses extends Operation {
this.outputType = "string";
this.args = [
{
"name": "IPv4",
"type": "boolean",
"value": true
name: "IPv4",
type: "boolean",
value: true
},
{
"name": "IPv6",
"type": "boolean",
"value": false
name: "IPv6",
type: "boolean",
value: false
},
{
"name": "Remove local IPv4 addresses",
"type": "boolean",
"value": false
name: "Remove local IPv4 addresses",
type: "boolean",
value: false
},
{
"name": "Display total",
"type": "boolean",
"value": false
name: "Display total",
type: "boolean",
value: false
},
{
name: "Sort",
type: "boolean",
value: false
},
{
name: "Unique",
type: "boolean",
value: false
}
];
}
@@ -53,7 +64,7 @@ class ExtractIPAddresses extends Operation {
* @returns {string}
*/
run(input, args) {
const [includeIpv4, includeIpv6, removeLocal, displayTotal] = args,
const [includeIpv4, includeIpv6, removeLocal, displayTotal, sort, unique] = args,
ipv4 = "(?:(?:\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d|\\d)(?:\\/\\d{1,2})?",
ipv6 = "((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})((([\\dA-F]{1,4}((?!\\3)::|:\\b|(?![\\dA-F])))|(?!\\2\\3)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})";
let ips = "";
@@ -66,23 +77,29 @@ class ExtractIPAddresses extends Operation {
ips = ipv6;
}
if (ips) {
const regex = new RegExp(ips, "ig");
if (!ips) return "";
if (removeLocal) {
const ten = "10\\..+",
oneninetwo = "192\\.168\\..+",
oneseventwo = "172\\.(?:1[6-9]|2\\d|3[01])\\..+",
onetwoseven = "127\\..+",
removeRegex = new RegExp("^(?:" + ten + "|" + oneninetwo +
"|" + oneseventwo + "|" + onetwoseven + ")");
const regex = new RegExp(ips, "ig");
return search(input, regex, removeRegex, displayTotal);
} else {
return search(input, regex, null, displayTotal);
}
const ten = "10\\..+",
oneninetwo = "192\\.168\\..+",
oneseventwo = "172\\.(?:1[6-9]|2\\d|3[01])\\..+",
onetwoseven = "127\\..+",
removeRegex = new RegExp("^(?:" + ten + "|" + oneninetwo +
"|" + oneseventwo + "|" + onetwoseven + ")");
const results = search(
input,
regex,
removeLocal ? removeRegex : null,
sort ? ipSort : null,
unique
);
if (displayTotal) {
return `Total found: ${results.length}\n\n${results.join("\n")}`;
} else {
return "";
return results.join("\n");
}
}

View File

@@ -6,6 +6,7 @@
import Operation from "../Operation.mjs";
import { search } from "../lib/Extract.mjs";
import { hexadecimalSort } from "../lib/Sort.mjs";
/**
* Extract MAC addresses operation
@@ -25,9 +26,19 @@ class ExtractMACAddresses extends Operation {
this.outputType = "string";
this.args = [
{
"name": "Display total",
"type": "boolean",
"value": false
name: "Display total",
type: "boolean",
value: false
},
{
name: "Sort",
type: "boolean",
value: false
},
{
name: "Unique",
type: "boolean",
value: false
}
];
}
@@ -38,10 +49,21 @@ class ExtractMACAddresses extends Operation {
* @returns {string}
*/
run(input, args) {
const displayTotal = args[0],
regex = /[A-F\d]{2}(?:[:-][A-F\d]{2}){5}/ig;
const [displayTotal, sort, unique] = args,
regex = /[A-F\d]{2}(?:[:-][A-F\d]{2}){5}/ig,
results = search(
input,
regex,
null,
sort ? hexadecimalSort : null,
unique
);
return search(input, regex, null, displayTotal);
if (displayTotal) {
return `Total found: ${results.length}\n\n${results.join("\n")}`;
} else {
return results.join("\n");
}
}
}

View File

@@ -6,6 +6,7 @@
import Operation from "../Operation.mjs";
import { search, URL_REGEX } from "../lib/Extract.mjs";
import { caseInsensitiveSort } from "../lib/Sort.mjs";
/**
* Extract URLs operation
@@ -25,9 +26,19 @@ class ExtractURLs extends Operation {
this.outputType = "string";
this.args = [
{
"name": "Display total",
"type": "boolean",
"value": false
name: "Display total",
type: "boolean",
value: false
},
{
name: "Sort",
type: "boolean",
value: false
},
{
name: "Unique",
type: "boolean",
value: false
}
];
}
@@ -38,8 +49,20 @@ class ExtractURLs extends Operation {
* @returns {string}
*/
run(input, args) {
const displayTotal = args[0];
return search(input, URL_REGEX, null, displayTotal);
const [displayTotal, sort, unique] = args;
const results = search(
input,
URL_REGEX,
null,
sort ? caseInsensitiveSort : null,
unique
);
if (displayTotal) {
return `Total found: ${results.length}\n\n${results.join("\n")}`;
} else {
return results.join("\n");
}
}
}

View File

@@ -30,7 +30,12 @@ class FrequencyDistribution extends Operation {
{
"name": "Show 0%s",
"type": "boolean",
"value": "Entropy.FREQ_ZEROS"
"value": true
},
{
"name": "Show ASCII",
"type": "boolean",
"value": true
}
];
}
@@ -76,14 +81,14 @@ class FrequencyDistribution extends Operation {
* @returns {html}
*/
present(freq, args) {
const showZeroes = args[0];
const [showZeroes, showAscii] = args;
// Print
let output = `<canvas id='chart-area'></canvas><br>
Total data length: ${freq.dataLength}
Number of bytes represented: ${freq.bytesRepresented}
Number of bytes not represented: ${256 - freq.bytesRepresented}
Byte Percentage
<script>
var canvas = document.getElementById("chart-area"),
parentRect = canvas.parentNode.getBoundingClientRect(),
@@ -93,16 +98,32 @@ Byte Percentage
canvas.height = parentRect.height * 0.9;
CanvasComponents.drawBarChart(canvas, scores, "Byte", "Frequency %", 16, 6);
</script>`;
</script>
<table class="table table-hover table-sm">
<tr><th>Byte</th>${showAscii ? "<th>ASCII</th>" : ""}<th>Percentage</th><th></th></tr>`;
for (let i = 0; i < 256; i++) {
if (freq.distribution[i] || showZeroes) {
output += " " + Utils.hex(i, 2) + " (" +
(freq.percentages[i].toFixed(2).replace(".00", "") + "%)").padEnd(8, " ") +
Array(Math.ceil(freq.percentages[i])+1).join("|") + "\n";
let c = "";
if (showAscii) {
if (i <= 32) {
c = String.fromCharCode(0x2400 + i);
} else if (i === 127) {
c = String.fromCharCode(0x2421);
} else {
c = String.fromCharCode(i);
}
}
const bite = `<td>${Utils.hex(i, 2)}</td>`,
ascii = showAscii ? `<td>${c}</td>` : "",
percentage = `<td>${(freq.percentages[i].toFixed(2).replace(".00", "") + "%").padEnd(8, " ")}</td>`,
bars = `<td>${Array(Math.ceil(freq.percentages[i])+1).join("|")}</td>`;
output += `<tr>${bite}${ascii}${percentage}${bars}</tr>`;
}
}
output += "</table>";
return output;
}

View File

@@ -95,7 +95,7 @@ class FromBCD extends Operation {
if (!packed) {
// Discard each high nibble
for (let i = 0; i < nibbles.length; i++) {
nibbles.splice(i, 1);
nibbles.splice(i, 1); // lgtm [js/loop-iteration-skipped-due-to-shifting]
}
}

View File

@@ -46,7 +46,7 @@ class FromBase extends Operation {
}
const number = input.replace(/\s/g, "").split(".");
let result = new BigNumber(number[0], radix) || 0;
let result = new BigNumber(number[0], radix);
if (number.length === 1) return result;

View File

@@ -84,10 +84,10 @@ class FromBase32 extends Operation {
chr5 = ((enc7 & 7) << 5) | enc8;
output.push(chr1);
if (enc2 & 3 !== 0 || enc3 !== 32) output.push(chr2);
if (enc4 & 15 !== 0 || enc5 !== 32) output.push(chr3);
if (enc5 & 1 !== 0 || enc6 !== 32) output.push(chr4);
if (enc7 & 7 !== 0 || enc8 !== 32) output.push(chr5);
if ((enc2 & 3) !== 0 || enc3 !== 32) output.push(chr2);
if ((enc4 & 15) !== 0 || enc5 !== 32) output.push(chr3);
if ((enc5 & 1) !== 0 || enc6 !== 32) output.push(chr4);
if ((enc7 & 7) !== 0 || enc8 !== 32) output.push(chr5);
}
return output;

View File

@@ -0,0 +1,89 @@
/**
* @author Thomas Weißschuh [thomas@t-8ch.de]
* @copyright Crown Copyright 2021
* @license Apache-2.0
*/
import {ALPHABET, highlightToBase45, highlightFromBase45} from "../lib/Base45.mjs";
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import Utils from "../Utils.mjs";
/**
* From Base45 operation
*/
class FromBase45 extends Operation {
/**
* FromBase45 constructor
*/
constructor() {
super();
this.name = "From Base45";
this.module = "Default";
this.description = "Base45 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers. The high number base results in shorter strings than with the decimal or hexadecimal system. Base45 is optimized for usage with QR codes.";
this.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems";
this.inputType = "string";
this.outputType = "byteArray";
this.args = [
{
name: "Alphabet",
type: "string",
value: ALPHABET
}
];
this.highlight = highlightFromBase45;
this.highlightReverse = highlightToBase45;
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {byteArray}
*/
run(input, args) {
if (!input) return [];
const alphabet = Utils.expandAlphRange(args[0]);
const res = [];
for (const triple of Utils.chunked(input, 3)) {
triple.reverse();
let b = 0;
for (const c of triple) {
const idx = alphabet.indexOf(c);
if (idx === -1) {
throw new OperationError(`Character not in alphabet: '${c}'`);
}
b *= 45;
b += idx;
}
if (b > 65535) {
throw new OperationError(`Triplet too large: '${triple.join("")}'`);
}
if (triple.length > 2) {
/**
* The last triple may only have 2 bytes so we push the MSB when we got 3 bytes
* Pushing MSB
*/
res.push(b >> 8);
}
/**
* Pushing LSB
*/
res.push(b & 0xff);
}
return res;
}
}
export default FromBase45;

View File

@@ -34,74 +34,99 @@ class FromBase64 extends Operation {
name: "Remove non-alphabet chars",
type: "boolean",
value: true
},
{
name: "Strict mode",
type: "boolean",
value: false
}
];
this.checks = [
{
pattern: "^\\s*(?:[A-Z\\d+/]{4})+(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$",
flags: "i",
args: ["A-Za-z0-9+/=", true]
args: ["A-Za-z0-9+/=", true, false]
},
{
pattern: "^\\s*[A-Z\\d\\-_]{20,}\\s*$",
flags: "i",
args: ["A-Za-z0-9-_", true]
args: ["A-Za-z0-9-_", true, false]
},
{
pattern: "^\\s*(?:[A-Z\\d+\\-]{4}){5,}(?:[A-Z\\d+\\-]{2}==|[A-Z\\d+\\-]{3}=)?\\s*$",
flags: "i",
args: ["A-Za-z0-9+\\-=", true]
args: ["A-Za-z0-9+\\-=", true, false]
},
{
pattern: "^\\s*(?:[A-Z\\d./]{4}){5,}(?:[A-Z\\d./]{2}==|[A-Z\\d./]{3}=)?\\s*$",
flags: "i",
args: ["./0-9A-Za-z=", true]
args: ["./0-9A-Za-z=", true, false]
},
{
pattern: "^\\s*[A-Z\\d_.]{20,}\\s*$",
flags: "i",
args: ["A-Za-z0-9_.", true]
args: ["A-Za-z0-9_.", true, false]
},
{
pattern: "^\\s*(?:[A-Z\\d._]{4}){5,}(?:[A-Z\\d._]{2}--|[A-Z\\d._]{3}-)?\\s*$",
flags: "i",
args: ["A-Za-z0-9._-", true]
args: ["A-Za-z0-9._-", true, false]
},
{
pattern: "^\\s*(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$",
flags: "i",
args: ["0-9a-zA-Z+/=", true]
args: ["0-9a-zA-Z+/=", true, false]
},
{
pattern: "^\\s*(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$",
flags: "i",
args: ["0-9A-Za-z+/=", true]
args: ["0-9A-Za-z+/=", true, false]
},
{
pattern: "^[ !\"#$%&'()*+,\\-./\\d:;<=>?@A-Z[\\\\\\]^_]{20,}$",
flags: "",
args: [" -_", false]
args: [" -_", false, false]
},
{
pattern: "^\\s*[A-Z\\d+\\-]{20,}\\s*$",
flags: "i",
args: ["+\\-0-9A-Za-z", true]
args: ["+\\-0-9A-Za-z", true, false]
},
{
pattern: "^\\s*[!\"#$%&'()*+,\\-0-689@A-NP-VX-Z[`a-fh-mp-r]{20,}\\s*$",
flags: "",
args: ["!-,-0-689@A-NP-VX-Z[`a-fh-mp-r", true]
args: ["!-,-0-689@A-NP-VX-Z[`a-fh-mp-r", true, false]
},
{
pattern: "^\\s*(?:[N-ZA-M\\d+/]{4}){5,}(?:[N-ZA-M\\d+/]{2}==|[N-ZA-M\\d+/]{3}=)?\\s*$",
flags: "i",
args: ["N-ZA-Mn-za-m0-9+/=", true]
args: ["N-ZA-Mn-za-m0-9+/=", true, false]
},
{
pattern: "^\\s*[A-Z\\d./]{20,}\\s*$",
flags: "i",
args: ["./0-9A-Za-z", true]
args: ["./0-9A-Za-z", true, false]
},
{
pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}CC|[A-Z=\\d\\+/]{3}C)?\\s*$",
flags: "i",
args: ["/128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC", true, false]
},
{
pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}55|[A-Z=\\d\\+/]{3}5)?\\s*$",
flags: "i",
args: ["3GHIJKLMNOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5", true, false]
},
{
pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}22|[A-Z=\\d\\+/]{3}2)?\\s*$",
flags: "i",
args: ["ZKj9n+yf0wDVX1s/5YbdxSo=ILaUpPBCHg8uvNO4klm6iJGhQ7eFrWczAMEq3RTt2", true, false]
},
{
pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}55|[A-Z=\\d\\+/]{3}5)?\\s*$",
flags: "i",
args: ["HNO4klm6ij9n+J2hyf0gzA8uvwDEq3X1Q7ZKeFrWcVTts/MRGYbdxSo=ILaUpPBC5", true, false]
}
];
}
@@ -111,9 +136,9 @@ class FromBase64 extends Operation {
* @returns {byteArray}
*/
run(input, args) {
const [alphabet, removeNonAlphChars] = args;
const [alphabet, removeNonAlphChars, strictMode] = args;
return fromBase64(input, alphabet, "byteArray", removeNonAlphChars);
return fromBase64(input, alphabet, "byteArray", removeNonAlphChars, strictMode);
}
/**

View File

@@ -35,7 +35,8 @@ class FromBinary extends Operation {
{
"name": "Byte Length",
"type": "number",
"value": 8
"value": 8,
"min": 1
}
];
this.checks = [

View File

@@ -0,0 +1,121 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2021
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import {fuzzyMatch, calcMatchRanges, DEFAULT_WEIGHTS} from "../lib/FuzzyMatch.mjs";
import Utils from "../Utils.mjs";
/**
* Fuzzy Match operation
*/
class FuzzyMatch extends Operation {
/**
* FuzzyMatch constructor
*/
constructor() {
super();
this.name = "Fuzzy Match";
this.module = "Default";
this.description = "Conducts a fuzzy search to find a pattern within the input based on weighted criteria.<br><br>e.g. A search for <code>dpan</code> will match on <code><b>D</b>on't <b>Pan</b>ic</code>";
this.infoURL = "https://wikipedia.org/wiki/Fuzzy_matching_(computer-assisted_translation)";
this.inputType = "string";
this.outputType = "html";
this.args = [
{
name: "Search",
type: "binaryString",
value: ""
},
{
name: "Sequential bonus",
type: "number",
value: DEFAULT_WEIGHTS.sequentialBonus,
hint: "Bonus for adjacent matches"
},
{
name: "Separator bonus",
type: "number",
value: DEFAULT_WEIGHTS.separatorBonus,
hint: "Bonus if match occurs after a separator"
},
{
name: "Camel bonus",
type: "number",
value: DEFAULT_WEIGHTS.camelBonus,
hint: "Bonus if match is uppercase and previous is lower"
},
{
name: "First letter bonus",
type: "number",
value: DEFAULT_WEIGHTS.firstLetterBonus,
hint: "Bonus if the first letter is matched"
},
{
name: "Leading letter penalty",
type: "number",
value: DEFAULT_WEIGHTS.leadingLetterPenalty,
hint: "Penalty applied for every letter in the input before the first match"
},
{
name: "Max leading letter penalty",
type: "number",
value: DEFAULT_WEIGHTS.maxLeadingLetterPenalty,
hint: "Maxiumum penalty for leading letters"
},
{
name: "Unmatched letter penalty",
type: "number",
value: DEFAULT_WEIGHTS.unmatchedLetterPenalty
},
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {html}
*/
run(input, args) {
const searchStr = args[0];
const weights = {
sequentialBonus: args[1],
separatorBonus: args[2],
camelBonus: args[3],
firstLetterBonus: args[4],
leadingLetterPenalty: args[5],
maxLeadingLetterPenalty: args[6],
unmatchedLetterPenalty: args[7]
};
const matches = fuzzyMatch(searchStr, input, true, weights);
if (!matches) {
return "No matches.";
}
let result = "", pos = 0, hlClass = "hl1";
matches.forEach(([matches, score, idxs]) => {
const matchRanges = calcMatchRanges(idxs);
matchRanges.forEach(([start, length], i) => {
result += Utils.escapeHtml(input.slice(pos, start));
if (i === 0) result += `<span class="${hlClass}">`;
pos = start + length;
result += `<b>${Utils.escapeHtml(input.slice(start, pos))}</b>`;
});
result += "</span>";
hlClass = hlClass === "hl1" ? "hl2" : "hl1";
});
result += Utils.escapeHtml(input.slice(pos, input.length));
return result;
}
}
export default FuzzyMatch;

View File

@@ -9,9 +9,11 @@
import Operation from "../Operation.mjs";
import kbpgp from "kbpgp";
import { getSubkeySize, ASP } from "../lib/PGP.mjs";
import { cryptNotice } from "../lib/Crypt.mjs";
import * as es6promisify from "es6-promisify";
const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify;
/**
* Generate PGP Key Pair operation
*/
@@ -25,7 +27,7 @@ class GeneratePGPKeyPair extends Operation {
this.name = "Generate PGP Key Pair";
this.module = "PGP";
this.description = "Generates a new public/private PGP key pair. Supports RSA and Eliptic Curve (EC) keys.";
this.description = `Generates a new public/private PGP key pair. Supports RSA and Eliptic Curve (EC) keys.<br><br>${cryptNotice}`;
this.infoURL = "https://wikipedia.org/wiki/Pretty_Good_Privacy";
this.inputType = "string";
this.outputType = "string";

View File

@@ -0,0 +1,88 @@
/**
* @author Matt C [me@mitt.dev]
* @author gchq77703 []
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import forge from "node-forge";
import { cryptNotice } from "../lib/Crypt.mjs";
/**
* Generate RSA Key Pair operation
*/
class GenerateRSAKeyPair extends Operation {
/**
* GenerateRSAKeyPair constructor
*/
constructor() {
super();
this.name = "Generate RSA Key Pair";
this.module = "Ciphers";
this.description = `Generate an RSA key pair with a given number of bits.<br><br>${cryptNotice}`;
this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "RSA Key Length",
type: "option",
value: [
"1024",
"2048",
"4096"
]
},
{
name: "Output Format",
type: "option",
value: [
"PEM",
"JSON",
"DER"
]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
async run(input, args) {
const [keyLength, outputFormat] = args;
return new Promise((resolve, reject) => {
forge.pki.rsa.generateKeyPair({
bits: Number(keyLength),
workers: -1,
workerScript: "assets/forge/prime.worker.min.js"
}, (err, keypair) => {
if (err) return reject(err);
let result;
switch (outputFormat) {
case "PEM":
result = forge.pki.publicKeyToPem(keypair.publicKey) + "\n" + forge.pki.privateKeyToPem(keypair.privateKey);
break;
case "JSON":
result = JSON.stringify(keypair);
break;
case "DER":
result = forge.asn1.toDer(forge.pki.privateKeyToAsn1(keypair.privateKey)).getBytes();
break;
}
resolve(result);
});
});
}
}
export default GenerateRSAKeyPair;

View File

@@ -0,0 +1,53 @@
/**
* @author n1073645 [n1073645@gmail.com]
* @copyright Crown Copyright 2020
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
/**
* Permutate String operation
*/
class GetAllCasings extends Operation {
/**
* GetAllCasings constructor
*/
constructor() {
super();
this.name = "Get All Casings";
this.module = "Default";
this.description = "Outputs all possible casing variations of a string.";
this.infoURL = "";
this.inputType = "string";
this.outputType = "string";
this.args = [];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const length = input.length;
const max = 1 << length;
input = input.toLowerCase();
let result = "";
for (let i = 0; i < max; i++) {
const temp = input.split("");
for (let j = 0; j < length; j++) {
if (((i >> j) & 1) === 1) {
temp[j] = temp[j].toUpperCase();
}
}
result += temp.join("") + "\n";
}
return result.slice(0, -1);
}
}
export default GetAllCasings;

View File

@@ -0,0 +1,63 @@
/**
* @author n1073645 [n1073645@gmail.com]
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2020
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import {UNITS} from "../lib/DateTime.mjs";
/**
* Get Time operation
*/
class GetTime extends Operation {
/**
* GetTime constructor
*/
constructor() {
super();
this.name = "Get Time";
this.module = "Default";
this.description = "Generates a timestamp showing the amount of time since the UNIX epoch (1970-01-01 00:00:00 UTC). Uses the W3C High Resolution Time API.";
this.infoURL = "https://wikipedia.org/wiki/Unix_time";
this.inputType = "string";
this.outputType = "number";
this.args = [
{
name: "Granularity",
type: "option",
value: UNITS
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {number}
*/
run(input, args) {
const nowMs = (performance.timeOrigin + performance.now()),
granularity = args[0];
switch (granularity) {
case "Nanoseconds (ns)":
return Math.round(nowMs * 1000 * 1000);
case "Microseconds (μs)":
return Math.round(nowMs * 1000);
case "Milliseconds (ms)":
return Math.round(nowMs);
case "Seconds (s)":
return Math.round(nowMs / 1000);
default:
throw new OperationError("Unknown granularity value: " + granularity);
}
}
}
export default GetTime;

View File

@@ -20,11 +20,19 @@ class HAS160 extends Operation {
this.name = "HAS-160";
this.module = "Crypto";
this.description = "HAS-160 is a cryptographic hash function designed for use with the Korean KCDSA digital signature algorithm. It is derived from SHA-1, with assorted changes intended to increase its security. It produces a 160-bit output.<br><br>HAS-160 is used in the same way as SHA-1. First it divides input in blocks of 512 bits each and pads the final block. A digest function updates the intermediate hash value by processing the input blocks in turn.<br><br>The message digest algorithm consists of 80 rounds.";
this.description = "HAS-160 is a cryptographic hash function designed for use with the Korean KCDSA digital signature algorithm. It is derived from SHA-1, with assorted changes intended to increase its security. It produces a 160-bit output.<br><br>HAS-160 is used in the same way as SHA-1. First it divides input in blocks of 512 bits each and pads the final block. A digest function updates the intermediate hash value by processing the input blocks in turn.<br><br>The message digest algorithm consists, by default, of 80 rounds.";
this.infoURL = "https://wikipedia.org/wiki/HAS-160";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [];
this.args = [
{
name: "Rounds",
type: "number",
value: 80,
min: 1,
max: 80
}
];
}
/**
@@ -33,7 +41,7 @@ class HAS160 extends Operation {
* @returns {string}
*/
run(input, args) {
return runHash("has160", input);
return runHash("has160", input, {rounds: args[0]});
}
}

View File

@@ -0,0 +1,166 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2021
* @license Apache-2.0
*
* HASSH created by Salesforce
* Ben Reardon (@benreardon)
* Adel Karimi (@0x4d31)
* and the JA3 crew:
* John B. Althouse
* Jeff Atkinson
* Josh Atkins
*
* Algorithm released under the BSD-3-clause licence
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import Utils from "../Utils.mjs";
import Stream from "../lib/Stream.mjs";
import {runHash} from "../lib/Hash.mjs";
/**
* HASSH Client Fingerprint operation
*/
class HASSHClientFingerprint extends Operation {
/**
* HASSHClientFingerprint constructor
*/
constructor() {
super();
this.name = "HASSH Client Fingerprint";
this.module = "Crypto";
this.description = "Generates a HASSH fingerprint to help identify SSH clients based on hashing together values from the Client Key Exchange Init message.<br><br>Input: A hex stream of the SSH_MSG_KEXINIT packet application layer from Client to Server.";
this.infoURL = "https://engineering.salesforce.com/open-sourcing-hassh-abed3ae5044c";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Input format",
type: "option",
value: ["Hex", "Base64", "Raw"]
},
{
name: "Output format",
type: "option",
value: ["Hash digest", "HASSH algorithms string", "Full details"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [inputFormat, outputFormat] = args;
input = Utils.convertToByteArray(input, inputFormat);
const s = new Stream(new Uint8Array(input));
// Length
const length = s.readInt(4);
if (s.length !== length + 4)
throw new OperationError("Incorrect packet length.");
// Padding length
const paddingLength = s.readInt(1);
// Message code
const messageCode = s.readInt(1);
if (messageCode !== 20)
throw new OperationError("Not a Key Exchange Init.");
// Cookie
s.moveForwardsBy(16);
// KEX Algorithms
const kexAlgosLength = s.readInt(4);
const kexAlgos = s.readString(kexAlgosLength);
// Server Host Key Algorithms
const serverHostKeyAlgosLength = s.readInt(4);
s.moveForwardsBy(serverHostKeyAlgosLength);
// Encryption Algorithms Client to Server
const encAlgosC2SLength = s.readInt(4);
const encAlgosC2S = s.readString(encAlgosC2SLength);
// Encryption Algorithms Server to Client
const encAlgosS2CLength = s.readInt(4);
s.moveForwardsBy(encAlgosS2CLength);
// MAC Algorithms Client to Server
const macAlgosC2SLength = s.readInt(4);
const macAlgosC2S = s.readString(macAlgosC2SLength);
// MAC Algorithms Server to Client
const macAlgosS2CLength = s.readInt(4);
s.moveForwardsBy(macAlgosS2CLength);
// Compression Algorithms Client to Server
const compAlgosC2SLength = s.readInt(4);
const compAlgosC2S = s.readString(compAlgosC2SLength);
// Compression Algorithms Server to Client
const compAlgosS2CLength = s.readInt(4);
s.moveForwardsBy(compAlgosS2CLength);
// Languages Client to Server
const langsC2SLength = s.readInt(4);
s.moveForwardsBy(langsC2SLength);
// Languages Server to Client
const langsS2CLength = s.readInt(4);
s.moveForwardsBy(langsS2CLength);
// First KEX packet follows
s.moveForwardsBy(1);
// Reserved
s.moveForwardsBy(4);
// Padding string
s.moveForwardsBy(paddingLength);
// Output
const hassh = [
kexAlgos,
encAlgosC2S,
macAlgosC2S,
compAlgosC2S
];
const hasshStr = hassh.join(";");
const hasshHash = runHash("md5", Utils.strToArrayBuffer(hasshStr));
switch (outputFormat) {
case "HASSH algorithms string":
return hasshStr;
case "Full details":
return `Hash digest:
${hasshHash}
Full HASSH algorithms string:
${hasshStr}
Key Exchange Algorithms:
${kexAlgos}
Encryption Algorithms Client to Server:
${encAlgosC2S}
MAC Algorithms Client to Server:
${macAlgosC2S}
Compression Algorithms Client to Server:
${compAlgosC2S}`;
case "Hash digest":
default:
return hasshHash;
}
}
}
export default HASSHClientFingerprint;

View File

@@ -0,0 +1,166 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2021
* @license Apache-2.0
*
* HASSH created by Salesforce
* Ben Reardon (@benreardon)
* Adel Karimi (@0x4d31)
* and the JA3 crew:
* John B. Althouse
* Jeff Atkinson
* Josh Atkins
*
* Algorithm released under the BSD-3-clause licence
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import Utils from "../Utils.mjs";
import Stream from "../lib/Stream.mjs";
import {runHash} from "../lib/Hash.mjs";
/**
* HASSH Server Fingerprint operation
*/
class HASSHServerFingerprint extends Operation {
/**
* HASSHServerFingerprint constructor
*/
constructor() {
super();
this.name = "HASSH Server Fingerprint";
this.module = "Crypto";
this.description = "Generates a HASSH fingerprint to help identify SSH servers based on hashing together values from the Server Key Exchange Init message.<br><br>Input: A hex stream of the SSH_MSG_KEXINIT packet application layer from Server to Client.";
this.infoURL = "https://engineering.salesforce.com/open-sourcing-hassh-abed3ae5044c";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Input format",
type: "option",
value: ["Hex", "Base64", "Raw"]
},
{
name: "Output format",
type: "option",
value: ["Hash digest", "HASSH algorithms string", "Full details"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [inputFormat, outputFormat] = args;
input = Utils.convertToByteArray(input, inputFormat);
const s = new Stream(new Uint8Array(input));
// Length
const length = s.readInt(4);
if (s.length !== length + 4)
throw new OperationError("Incorrect packet length.");
// Padding length
const paddingLength = s.readInt(1);
// Message code
const messageCode = s.readInt(1);
if (messageCode !== 20)
throw new OperationError("Not a Key Exchange Init.");
// Cookie
s.moveForwardsBy(16);
// KEX Algorithms
const kexAlgosLength = s.readInt(4);
const kexAlgos = s.readString(kexAlgosLength);
// Server Host Key Algorithms
const serverHostKeyAlgosLength = s.readInt(4);
s.moveForwardsBy(serverHostKeyAlgosLength);
// Encryption Algorithms Client to Server
const encAlgosC2SLength = s.readInt(4);
s.moveForwardsBy(encAlgosC2SLength);
// Encryption Algorithms Server to Client
const encAlgosS2CLength = s.readInt(4);
const encAlgosS2C = s.readString(encAlgosS2CLength);
// MAC Algorithms Client to Server
const macAlgosC2SLength = s.readInt(4);
s.moveForwardsBy(macAlgosC2SLength);
// MAC Algorithms Server to Client
const macAlgosS2CLength = s.readInt(4);
const macAlgosS2C = s.readString(macAlgosS2CLength);
// Compression Algorithms Client to Server
const compAlgosC2SLength = s.readInt(4);
s.moveForwardsBy(compAlgosC2SLength);
// Compression Algorithms Server to Client
const compAlgosS2CLength = s.readInt(4);
const compAlgosS2C = s.readString(compAlgosS2CLength);
// Languages Client to Server
const langsC2SLength = s.readInt(4);
s.moveForwardsBy(langsC2SLength);
// Languages Server to Client
const langsS2CLength = s.readInt(4);
s.moveForwardsBy(langsS2CLength);
// First KEX packet follows
s.moveForwardsBy(1);
// Reserved
s.moveForwardsBy(4);
// Padding string
s.moveForwardsBy(paddingLength);
// Output
const hassh = [
kexAlgos,
encAlgosS2C,
macAlgosS2C,
compAlgosS2C
];
const hasshStr = hassh.join(";");
const hasshHash = runHash("md5", Utils.strToArrayBuffer(hasshStr));
switch (outputFormat) {
case "HASSH algorithms string":
return hasshStr;
case "Full details":
return `Hash digest:
${hasshHash}
Full HASSH algorithms string:
${hasshStr}
Key Exchange Algorithms:
${kexAlgos}
Encryption Algorithms Server to Client:
${encAlgosS2C}
MAC Algorithms Server to Client:
${macAlgosS2C}
Compression Algorithms Server to Client:
${compAlgosS2C}`;
case "Hash digest":
default:
return hasshHash;
}
}
}
export default HASSHServerFingerprint;

View File

@@ -0,0 +1,205 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2021
* @license Apache-2.0
*
* JA3 created by Salesforce
* John B. Althouse
* Jeff Atkinson
* Josh Atkins
*
* Algorithm released under the BSD-3-clause licence
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import Utils from "../Utils.mjs";
import Stream from "../lib/Stream.mjs";
import {runHash} from "../lib/Hash.mjs";
/**
* JA3 Fingerprint operation
*/
class JA3Fingerprint extends Operation {
/**
* JA3Fingerprint constructor
*/
constructor() {
super();
this.name = "JA3 Fingerprint";
this.module = "Crypto";
this.description = "Generates a JA3 fingerprint to help identify TLS clients based on hashing together values from the Client Hello.<br><br>Input: A hex stream of the TLS Client Hello packet application layer.";
this.infoURL = "https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Input format",
type: "option",
value: ["Hex", "Base64", "Raw"]
},
{
name: "Output format",
type: "option",
value: ["Hash digest", "JA3 string", "Full details"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [inputFormat, outputFormat] = args;
input = Utils.convertToByteArray(input, inputFormat);
const s = new Stream(new Uint8Array(input));
const handshake = s.readInt(1);
if (handshake !== 0x16)
throw new OperationError("Not handshake data.");
// Version
s.moveForwardsBy(2);
// Length
const length = s.readInt(2);
if (s.length !== length + 5)
throw new OperationError("Incorrect handshake length.");
// Handshake type
const handshakeType = s.readInt(1);
if (handshakeType !== 1)
throw new OperationError("Not a Client Hello.");
// Handshake length
const handshakeLength = s.readInt(3);
if (s.length !== handshakeLength + 9)
throw new OperationError("Not enough data in Client Hello.");
// Hello version
const helloVersion = s.readInt(2);
// Random
s.moveForwardsBy(32);
// Session ID
const sessionIDLength = s.readInt(1);
s.moveForwardsBy(sessionIDLength);
// Cipher suites
const cipherSuitesLength = s.readInt(2);
const cipherSuites = s.getBytes(cipherSuitesLength);
const cs = new Stream(cipherSuites);
const cipherSegment = parseJA3Segment(cs, 2);
// Compression Methods
const compressionMethodsLength = s.readInt(1);
s.moveForwardsBy(compressionMethodsLength);
// Extensions
const extensionsLength = s.readInt(2);
const extensions = s.getBytes(extensionsLength);
const es = new Stream(extensions);
let ecsLen, ecs, ellipticCurves = "", ellipticCurvePointFormats = "";
const exts = [];
while (es.hasMore()) {
const type = es.readInt(2);
const length = es.readInt(2);
switch (type) {
case 0x0a: // Elliptic curves
ecsLen = es.readInt(2);
ecs = new Stream(es.getBytes(ecsLen));
ellipticCurves = parseJA3Segment(ecs, 2);
break;
case 0x0b: // Elliptic curve point formats
ecsLen = es.readInt(1);
ecs = new Stream(es.getBytes(ecsLen));
ellipticCurvePointFormats = parseJA3Segment(ecs, 1);
break;
default:
es.moveForwardsBy(length);
}
if (!GREASE_CIPHERSUITES.includes(type))
exts.push(type);
}
// Output
const ja3 = [
helloVersion.toString(),
cipherSegment,
exts.join("-"),
ellipticCurves,
ellipticCurvePointFormats
];
const ja3Str = ja3.join(",");
const ja3Hash = runHash("md5", Utils.strToArrayBuffer(ja3Str));
switch (outputFormat) {
case "JA3 string":
return ja3Str;
case "Full details":
return `Hash digest:
${ja3Hash}
Full JA3 string:
${ja3Str}
TLS Version:
${helloVersion.toString()}
Cipher Suites:
${cipherSegment}
Extensions:
${exts.join("-")}
Elliptic Curves:
${ellipticCurves}
Elliptic Curve Point Formats:
${ellipticCurvePointFormats}`;
case "Hash digest":
default:
return ja3Hash;
}
}
}
/**
* Parses a JA3 segment, returning a "-" separated list
*
* @param {Stream} stream
* @returns {string}
*/
function parseJA3Segment(stream, size=2) {
const segment = [];
while (stream.hasMore()) {
const element = stream.readInt(size);
if (!GREASE_CIPHERSUITES.includes(element))
segment.push(element);
}
return segment.join("-");
}
const GREASE_CIPHERSUITES = [
0x0a0a,
0x1a1a,
0x2a2a,
0x3a3a,
0x4a4a,
0x5a5a,
0x6a6a,
0x7a7a,
0x8a8a,
0x9a9a,
0xaaaa,
0xbaba,
0xcaca,
0xdada,
0xeaea,
0xfafa
];
export default JA3Fingerprint;

View File

@@ -0,0 +1,145 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2021
* @license Apache-2.0
*
* JA3S created by Salesforce
* John B. Althouse
* Jeff Atkinson
* Josh Atkins
*
* Algorithm released under the BSD-3-clause licence
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import Utils from "../Utils.mjs";
import Stream from "../lib/Stream.mjs";
import {runHash} from "../lib/Hash.mjs";
/**
* JA3S Fingerprint operation
*/
class JA3SFingerprint extends Operation {
/**
* JA3SFingerprint constructor
*/
constructor() {
super();
this.name = "JA3S Fingerprint";
this.module = "Crypto";
this.description = "Generates a JA3S fingerprint to help identify TLS servers based on hashing together values from the Server Hello.<br><br>Input: A hex stream of the TLS Server Hello record application layer.";
this.infoURL = "https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Input format",
type: "option",
value: ["Hex", "Base64", "Raw"]
},
{
name: "Output format",
type: "option",
value: ["Hash digest", "JA3S string", "Full details"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [inputFormat, outputFormat] = args;
input = Utils.convertToByteArray(input, inputFormat);
const s = new Stream(new Uint8Array(input));
const handshake = s.readInt(1);
if (handshake !== 0x16)
throw new OperationError("Not handshake data.");
// Version
s.moveForwardsBy(2);
// Length
const length = s.readInt(2);
if (s.length !== length + 5)
throw new OperationError("Incorrect handshake length.");
// Handshake type
const handshakeType = s.readInt(1);
if (handshakeType !== 2)
throw new OperationError("Not a Server Hello.");
// Handshake length
const handshakeLength = s.readInt(3);
if (s.length !== handshakeLength + 9)
throw new OperationError("Not enough data in Server Hello.");
// Hello version
const helloVersion = s.readInt(2);
// Random
s.moveForwardsBy(32);
// Session ID
const sessionIDLength = s.readInt(1);
s.moveForwardsBy(sessionIDLength);
// Cipher suite
const cipherSuite = s.readInt(2);
// Compression Method
s.moveForwardsBy(1);
// Extensions
const extensionsLength = s.readInt(2);
const extensions = s.getBytes(extensionsLength);
const es = new Stream(extensions);
const exts = [];
while (es.hasMore()) {
const type = es.readInt(2);
const length = es.readInt(2);
es.moveForwardsBy(length);
exts.push(type);
}
// Output
const ja3s = [
helloVersion.toString(),
cipherSuite,
exts.join("-")
];
const ja3sStr = ja3s.join(",");
const ja3sHash = runHash("md5", Utils.strToArrayBuffer(ja3sStr));
switch (outputFormat) {
case "JA3S string":
return ja3sStr;
case "Full details":
return `Hash digest:
${ja3sHash}
Full JA3S string:
${ja3sStr}
TLS Version:
${helloVersion.toString()}
Cipher Suite:
${cipherSuite}
Extensions:
${exts.join("-")}`;
case "Hash digest":
default:
return ja3sHash;
}
}
}
export default JA3SFingerprint;

View File

@@ -6,6 +6,8 @@
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import * as flat from "flat";
const flatten = flat.default ? flat.default.flatten : flat.flatten;
/**
* JSON to CSV operation
@@ -38,6 +40,41 @@ class JSONToCSV extends Operation {
];
}
/**
* Converts JSON to a CSV equivalent.
*
* @param {boolean} force - Whether to force conversion of data to fit in a cell
* @returns {string}
*/
toCSV(force=false) {
const self = this;
// If the JSON is an array of arrays, this is easy
if (this.flattened[0] instanceof Array) {
return this.flattened
.map(row => row
.map(d => self.escapeCellContents(d, force))
.join(this.cellDelim)
)
.join(this.rowDelim) +
this.rowDelim;
}
// If it's an array of dictionaries...
const header = Object.keys(this.flattened[0]);
return header
.map(d => self.escapeCellContents(d, force))
.join(this.cellDelim) +
this.rowDelim +
this.flattened
.map(row => header
.map(h => row[h])
.map(d => self.escapeCellContents(d, force))
.join(this.cellDelim)
)
.join(this.rowDelim) +
this.rowDelim;
}
/**
* @param {JSON} input
* @param {Object[]} args
@@ -49,40 +86,23 @@ class JSONToCSV extends Operation {
// Record values so they don't have to be passed to other functions explicitly
this.cellDelim = cellDelim;
this.rowDelim = rowDelim;
const self = this;
if (!(input instanceof Array)) {
input = [input];
this.flattened = input;
if (!(this.flattened instanceof Array)) {
this.flattened = [input];
}
try {
// If the JSON is an array of arrays, this is easy
if (input[0] instanceof Array) {
return input
.map(row => row
.map(self.escapeCellContents.bind(self))
.join(cellDelim)
)
.join(rowDelim) +
rowDelim;
}
// If it's an array of dictionaries...
const header = Object.keys(input[0]);
return header
.map(self.escapeCellContents.bind(self))
.join(cellDelim) +
rowDelim +
input
.map(row => header
.map(h => row[h])
.map(self.escapeCellContents.bind(self))
.join(cellDelim)
)
.join(rowDelim) +
rowDelim;
return this.toCSV();
} catch (err) {
throw new OperationError("Unable to parse JSON to CSV: " + err.toString());
try {
this.flattened = flatten(input);
if (!(this.flattened instanceof Array)) {
this.flattened = [this.flattened];
}
return this.toCSV(true);
} catch (err) {
throw new OperationError("Unable to parse JSON to CSV: " + err.toString());
}
}
}
@@ -90,15 +110,17 @@ class JSONToCSV extends Operation {
* Correctly escapes a cell's contents based on the cell and row delimiters.
*
* @param {string} data
* @param {boolean} force - Whether to force conversion of data to fit in a cell
* @returns {string}
*/
escapeCellContents(data) {
escapeCellContents(data, force=false) {
if (typeof data === "number") data = data.toString();
if (force && typeof data !== "string") data = JSON.stringify(data);
// Double quotes should be doubled up
data = data.replace(/"/g, '""');
// If the cell contains a cell or row delimiter or a double quote, it mut be enclosed in double quotes
// If the cell contains a cell or row delimiter or a double quote, it must be enclosed in double quotes
if (
data.indexOf(this.cellDelim) >= 0 ||
data.indexOf(this.rowDelim) >= 0 ||

View File

@@ -3,10 +3,11 @@
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import jwt from "jsonwebtoken";
import OperationError from "../errors/OperationError.mjs";
import {JWT_ALGORITHMS} from "../lib/JWT.mjs";
/**
* JWT Sign operation
@@ -34,18 +35,7 @@ class JWTSign extends Operation {
{
name: "Signing algorithm",
type: "option",
value: [
"HS256",
"HS384",
"HS512",
"RS256",
"RS384",
"RS512",
"ES256",
"ES384",
"ES512",
"None"
]
value: JWT_ALGORITHMS
}
];
}

View File

@@ -3,10 +3,11 @@
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import jwt from "jsonwebtoken";
import OperationError from "../errors/OperationError.mjs";
import {JWT_ALGORITHMS} from "../lib/JWT.mjs";
/**
* JWT Verify operation
@@ -27,7 +28,7 @@ class JWTVerify extends Operation {
this.outputType = "JSON";
this.args = [
{
name: "Private/Secret Key",
name: "Public/Secret Key",
type: "text",
value: "secret"
},
@@ -41,14 +42,11 @@ class JWTVerify extends Operation {
*/
run(input, args) {
const [key] = args;
const algos = JWT_ALGORITHMS;
algos[algos.indexOf("None")] = "none";
try {
const verified = jwt.verify(input, key, { algorithms: [
"HS256",
"HS384",
"HS512",
"none"
]});
const verified = jwt.verify(input, key, { algorithms: algos });
if (Object.prototype.hasOwnProperty.call(verified, "name") && verified.name === "JsonWebTokenError") {
throw new OperationError(verified.message);

View File

@@ -1,6 +1,9 @@
/**
* Emulation of the Lorenz SZ40/42a/42b cipher attachment.
*
* Tested against the Colossus Rebuild at Bletchley Park's TNMOC
* using a variety of inputs and settings to confirm correctness.
*
* @author VirtualColossus [martin@virtualcolossus.co.uk]
* @copyright Crown Copyright 2019
* @license Apache-2.0
@@ -60,7 +63,8 @@ class Lorenz extends Operation {
},
{
name: "KT-Schalter",
type: "boolean"
type: "boolean",
value: false
},
{
name: "Mode",
@@ -375,7 +379,7 @@ class Lorenz extends Operation {
// Psi wheels only move sometimes, dependent on M37 current setting and limitations
const basicmotor = m37lug;
let totalmotor = basicmotor;
let totalmotor;
let lim = 0;
p5[2] = p5[1];

View File

@@ -20,11 +20,18 @@ class MD2 extends Operation {
this.name = "MD2";
this.module = "Crypto";
this.description = "The MD2 (Message-Digest 2) algorithm is a cryptographic hash function developed by Ronald Rivest in 1989. The algorithm is optimized for 8-bit computers.<br><br>Although MD2 is no longer considered secure, even as of 2014, it remains in use in public key infrastructures as part of certificates generated with MD2 and RSA.";
this.description = "The MD2 (Message-Digest 2) algorithm is a cryptographic hash function developed by Ronald Rivest in 1989. The algorithm is optimized for 8-bit computers.<br><br>Although MD2 is no longer considered secure, even as of 2014, it remains in use in public key infrastructures as part of certificates generated with MD2 and RSA. The message digest algorithm consists, by default, of 18 rounds.";
this.infoURL = "https://wikipedia.org/wiki/MD2_(cryptography)";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [];
this.args = [
{
name: "Rounds",
type: "number",
value: 18,
min: 0
}
];
}
/**
@@ -33,7 +40,7 @@ class MD2 extends Operation {
* @returns {string}
*/
run(input, args) {
return runHash("md2", input);
return runHash("md2", input, {rounds: args[0]});
}
}

View File

@@ -24,6 +24,13 @@ class MicrosoftScriptDecoder extends Operation {
this.inputType = "string";
this.outputType = "string";
this.args = [];
this.checks = [
{
pattern: "#@~\\^.{6}==(.+).{6}==\\^#~@",
flags: "i",
args: []
}
];
}
/**

View File

@@ -51,7 +51,7 @@ class OpticalCharacterRecognition extends Operation {
async run(input, args) {
const [showConfidence] = args;
if (!isWorkerEnvironment()) throw OperationError("This operation only works in a browser");
if (!isWorkerEnvironment()) throw new OperationError("This operation only works in a browser");
const type = isImage(input);
if (!type) {

View File

@@ -111,7 +111,7 @@ class PHPDeserialize extends Operation {
} else {
const numberCheck = lastItem.match(/[0-9]+/);
if (args[0] && numberCheck && numberCheck[0].length === lastItem.length) {
result.push("\"" + lastItem + "\": " + item);
result.push('"' + lastItem + '": ' + item);
} else {
result.push(lastItem + ": " + item);
}
@@ -149,11 +149,11 @@ class PHPDeserialize extends Operation {
const length = readUntil(":");
expect("\"");
const value = read(length);
expect("\";");
expect('";');
if (args[0]) {
return "\"" + value.replace(/"/g, "\\\"") + "\"";
return '"' + value.replace(/"/g, '\\"') + '"'; // lgtm [js/incomplete-sanitization]
} else {
return "\"" + value + "\"";
return '"' + value + '"';
}
}

View File

@@ -72,7 +72,7 @@ class ParseDateTime extends Operation {
"\nLeap year: " + date.isLeapYear() +
"\nDays in this month: " + date.daysInMonth() +
"\n\nDay of year: " + date.dayOfYear() +
"\nWeek number: " + date.weekYear() +
"\nWeek number: " + date.week() +
"\nQuarter: " + date.quarter();
return output;

View File

@@ -165,11 +165,94 @@ class ParseIPv6Address extends Operation {
// Multicast
output += "\nThis is a reserved multicast address.";
output += "\nMulticast addresses range: ff00::/8";
switch (ipv6[0]) {
case 0xff01:
output += "\n\nReserved Multicast Block for Interface Local Scope";
break;
case 0xff02:
output += "\n\nReserved Multicast Block for Link Local Scope";
break;
case 0xff03:
output += "\n\nReserved Multicast Block for Realm Local Scope";
break;
case 0xff04:
output += "\n\nReserved Multicast Block for Admin Local Scope";
break;
case 0xff05:
output += "\n\nReserved Multicast Block for Site Local Scope";
break;
case 0xff08:
output += "\n\nReserved Multicast Block for Organisation Local Scope";
break;
case 0xff0e:
output += "\n\nReserved Multicast Block for Global Scope";
break;
}
if (ipv6[6] === 1) {
if (ipv6[7] === 2) {
output += "\nReserved Multicast Address for 'All DHCP Servers and Relay Agents (defined in RFC3315)'";
} else if (ipv6[7] === 3) {
output += "\nReserved Multicast Address for 'All LLMNR Hosts (defined in RFC4795)'";
}
} else {
switch (ipv6[7]) {
case 1:
output += "\nReserved Multicast Address for 'All nodes'";
break;
case 2:
output += "\nReserved Multicast Address for 'All routers'";
break;
case 5:
output += "\nReserved Multicast Address for 'OSPFv3 - All OSPF routers'";
break;
case 6:
output += "\nReserved Multicast Address for 'OSPFv3 - All Designated Routers'";
break;
case 8:
output += "\nReserved Multicast Address for 'IS-IS for IPv6 Routers'";
break;
case 9:
output += "\nReserved Multicast Address for 'RIP Routers'";
break;
case 0xa:
output += "\nReserved Multicast Address for 'EIGRP Routers'";
break;
case 0xc:
output += "\nReserved Multicast Address for 'Simple Service Discovery Protocol'";
break;
case 0xd:
output += "\nReserved Multicast Address for 'PIM Routers'";
break;
case 0x16:
output += "\nReserved Multicast Address for 'MLDv2 Reports (defined in RFC3810)'";
break;
case 0x6b:
output += "\nReserved Multicast Address for 'Precision Time Protocol v2 Peer Delay Measurement Messages'";
break;
case 0xfb:
output += "\nReserved Multicast Address for 'Multicast DNS'";
break;
case 0x101:
output += "\nReserved Multicast Address for 'Network Time Protocol'";
break;
case 0x108:
output += "\nReserved Multicast Address for 'Network Information Service'";
break;
case 0x114:
output += "\nReserved Multicast Address for 'Experiments'";
break;
case 0x181:
output += "\nReserved Multicast Address for 'Precision Time Protocol v2 Messages (exc. Peer Delay)'";
break;
}
}
}
// Detect possible EUI-64 addresses
if ((ipv6[5] & 0xff === 0xff) && (ipv6[6] >>> 8 === 0xfe)) {
if (((ipv6[5] & 0xff) === 0xff) && (ipv6[6] >>> 8 === 0xfe)) {
output += "\n\nThis IPv6 address contains a modified EUI-64 address, identified by the presence of FF:FE in the 12th and 13th octets.";
const intIdent = Utils.hex(ipv6[4] >>> 8) + ":" + Utils.hex(ipv6[4] & 0xff) + ":" +

View File

@@ -87,7 +87,7 @@ class ParseSSHHostKey extends Operation {
* @returns {byteArray}
*/
convertKeyToBinary(inputKey, inputFormat) {
const keyPattern = new RegExp(/^(?:[ssh]|[ecdsa-sha2])\S+\s+(\S*)/),
const keyPattern = new RegExp(/^(?:ssh|ecdsa-sha2)\S+\s+(\S*)/),
keyMatch = inputKey.match(keyPattern);
if (keyMatch) {

View File

@@ -0,0 +1,245 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2022
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import Stream from "../lib/Stream.mjs";
import {toHexFast, fromHex} from "../lib/Hex.mjs";
import {toBinary} from "../lib/Binary.mjs";
import {objToTable, bytesToLargeNumber} from "../lib/Protocol.mjs";
import Utils from "../Utils.mjs";
import OperationError from "../errors/OperationError.mjs";
import BigNumber from "bignumber.js";
/**
* Parse TCP operation
*/
class ParseTCP extends Operation {
/**
* ParseTCP constructor
*/
constructor() {
super();
this.name = "Parse TCP";
this.module = "Default";
this.description = "Parses a TCP header and payload (if present).";
this.infoURL = "https://wikipedia.org/wiki/Transmission_Control_Protocol";
this.inputType = "string";
this.outputType = "json";
this.presentType = "html";
this.args = [
{
name: "Input format",
type: "option",
value: ["Hex", "Raw"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {html}
*/
run(input, args) {
const format = args[0];
if (format === "Hex") {
input = fromHex(input);
} else if (format === "Raw") {
input = Utils.strToArrayBuffer(input);
} else {
throw new OperationError("Unrecognised input format.");
}
const s = new Stream(new Uint8Array(input));
if (s.length < 20) {
throw new OperationError("Need at least 20 bytes for a TCP Header");
}
// Parse Header
const TCPPacket = {
"Source port": s.readInt(2),
"Destination port": s.readInt(2),
"Sequence number": bytesToLargeNumber(s.getBytes(4)),
"Acknowledgement number": s.readInt(4),
"Data offset": s.readBits(4),
"Flags": {
"Reserved": toBinary(s.readBits(3), "", 3),
"NS": s.readBits(1),
"CWR": s.readBits(1),
"ECE": s.readBits(1),
"URG": s.readBits(1),
"ACK": s.readBits(1),
"PSH": s.readBits(1),
"RST": s.readBits(1),
"SYN": s.readBits(1),
"FIN": s.readBits(1),
},
"Window size": s.readInt(2),
"Checksum": "0x" + toHexFast(s.getBytes(2)),
"Urgent pointer": "0x" + toHexFast(s.getBytes(2))
};
// Parse options if present
let windowScaleShift = 0;
if (TCPPacket["Data offset"] > 5) {
let remainingLength = TCPPacket["Data offset"] * 4 - 20;
const options = {};
while (remainingLength > 0) {
const option = {
"Kind": s.readInt(1)
};
let opt = { name: "Reserved", length: true };
if (Object.prototype.hasOwnProperty.call(TCP_OPTION_KIND_LOOKUP, option.Kind)) {
opt = TCP_OPTION_KIND_LOOKUP[option.Kind];
}
// Add Length and Value fields
if (opt.length) {
option.Length = s.readInt(1);
if (option.Length > 2) {
if (Object.prototype.hasOwnProperty.call(opt, "parser")) {
option.Value = opt.parser(s.getBytes(option.Length - 2));
} else {
option.Value = option.Length <= 6 ?
s.readInt(option.Length - 2):
"0x" + toHexFast(s.getBytes(option.Length - 2));
}
// Store Window Scale shift for later
if (option.Kind === 3 && option.Value) {
windowScaleShift = option.Value["Shift count"];
}
}
}
options[opt.name] = option;
const length = option.Length || 1;
remainingLength -= length;
}
TCPPacket.Options = options;
}
if (s.hasMore()) {
TCPPacket.Data = "0x" + toHexFast(s.getBytes());
}
// Improve values
TCPPacket["Data offset"] = `${TCPPacket["Data offset"]} (${TCPPacket["Data offset"] * 4} bytes)`;
const trueWndSize = BigNumber(TCPPacket["Window size"]).multipliedBy(BigNumber(2).pow(BigNumber(windowScaleShift)));
TCPPacket["Window size"] = `${TCPPacket["Window size"]} (Scaled: ${trueWndSize})`;
return TCPPacket;
}
/**
* Displays the TCP Packet in a tabular style
* @param {Object} data
* @returns {html}
*/
present(data) {
return objToTable(data);
}
}
// Taken from https://www.iana.org/assignments/tcp-parameters/tcp-parameters.xhtml
// on 2022-05-30
const TCP_OPTION_KIND_LOOKUP = {
0: { name: "End of Option List", length: false },
1: { name: "No-Operation", length: false },
2: { name: "Maximum Segment Size", length: true },
3: { name: "Window Scale", length: true, parser: windowScaleParser },
4: { name: "SACK Permitted", length: true },
5: { name: "SACK", length: true },
6: { name: "Echo (obsoleted by option 8)", length: true },
7: { name: "Echo Reply (obsoleted by option 8)", length: true },
8: { name: "Timestamps", length: true, parser: tcpTimestampParser },
9: { name: "Partial Order Connection Permitted (obsolete)", length: true },
10: { name: "Partial Order Service Profile (obsolete)", length: true },
11: { name: "CC (obsolete)", length: true },
12: { name: "CC.NEW (obsolete)", length: true },
13: { name: "CC.ECHO (obsolete)", length: true },
14: { name: "TCP Alternate Checksum Request (obsolete)", length: true, parser: tcpAlternateChecksumParser },
15: { name: "TCP Alternate Checksum Data (obsolete)", length: true },
16: { name: "Skeeter", length: true },
17: { name: "Bubba", length: true },
18: { name: "Trailer Checksum Option", length: true },
19: { name: "MD5 Signature Option (obsoleted by option 29)", length: true },
20: { name: "SCPS Capabilities", length: true },
21: { name: "Selective Negative Acknowledgements", length: true },
22: { name: "Record Boundaries", length: true },
23: { name: "Corruption experienced", length: true },
24: { name: "SNAP", length: true },
25: { name: "Unassigned (released 2000-12-18)", length: true },
26: { name: "TCP Compression Filter", length: true },
27: { name: "Quick-Start Response", length: true },
28: { name: "User Timeout Option (also, other known unauthorized use)", length: true },
29: { name: "TCP Authentication Option (TCP-AO)", length: true },
30: { name: "Multipath TCP (MPTCP)", length: true },
69: { name: "Encryption Negotiation (TCP-ENO)", length: true },
70: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true },
76: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true },
77: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true },
78: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true },
253: { name: "RFC3692-style Experiment 1 (also improperly used for shipping products) ", length: true },
254: { name: "RFC3692-style Experiment 2 (also improperly used for shipping products) ", length: true }
};
/**
* Parses the TCP Alternate Checksum Request field
* @param {Uint8Array} data
*/
function tcpAlternateChecksumParser(data) {
const lookup = {
0: "TCP Checksum",
1: "8-bit Fletchers's algorithm",
2: "16-bit Fletchers's algorithm",
3: "Redundant Checksum Avoidance"
}[data[0]];
return `${lookup} (0x${toHexFast(data)})`;
}
/**
* Parses the TCP Timestamp field
* @param {Uint8Array} data
*/
function tcpTimestampParser(data) {
const s = new Stream(data);
if (s.length !== 8)
return `Error: Timestamp field should be 8 bytes long (received 0x${toHexFast(data)})`;
const tsval = bytesToLargeNumber(s.getBytes(4)),
tsecr = bytesToLargeNumber(s.getBytes(4));
return {
"Current Timestamp": tsval,
"Echo Reply": tsecr
};
}
/**
* Parses the Window Scale field
* @param {Uint8Array} data
*/
function windowScaleParser(data) {
if (data.length !== 1)
return `Error: Window Scale should be one byte long (received 0x${toHexFast(data)})`;
return {
"Shift count": data[0],
"Multiplier": 1 << data[0]
};
}
export default ParseTCP;

View File

@@ -6,7 +6,9 @@
import Operation from "../Operation.mjs";
import Stream from "../lib/Stream.mjs";
import {toHex} from "../lib/Hex.mjs";
import {toHexFast, fromHex} from "../lib/Hex.mjs";
import {objToTable} from "../lib/Protocol.mjs";
import Utils from "../Utils.mjs";
import OperationError from "../errors/OperationError.mjs";
/**
@@ -24,58 +26,61 @@ class ParseUDP extends Operation {
this.module = "Default";
this.description = "Parses a UDP header and payload (if present).";
this.infoURL = "https://wikipedia.org/wiki/User_Datagram_Protocol";
this.inputType = "ArrayBuffer";
this.inputType = "string";
this.outputType = "json";
this.presentType = "html";
this.args = [];
this.args = [
{
name: "Input format",
type: "option",
value: ["Hex", "Raw"]
}
];
}
/**
* @param {ArrayBuffer} input
* @param {string} input
* @param {Object[]} args
* @returns {Object}
*/
run(input, args) {
if (input.byteLength < 8) {
throw new OperationError("Need 8 bytes for a UDP Header");
const format = args[0];
if (format === "Hex") {
input = fromHex(input);
} else if (format === "Raw") {
input = Utils.strToArrayBuffer(input);
} else {
throw new OperationError("Unrecognised input format.");
}
const s = new Stream(new Uint8Array(input));
if (s.length < 8) {
throw new OperationError("Need 8 bytes for a UDP Header");
}
// Parse Header
const UDPPacket = {
"Source port": s.readInt(2),
"Destination port": s.readInt(2),
"Length": s.readInt(2),
"Checksum": toHex(s.getBytes(2), "")
"Checksum": "0x" + toHexFast(s.getBytes(2))
};
// Parse data if present
if (s.hasMore()) {
UDPPacket.Data = toHex(s.getBytes(UDPPacket.Length - 8), "");
UDPPacket.Data = "0x" + toHexFast(s.getBytes(UDPPacket.Length - 8));
}
return UDPPacket;
}
/**
* Displays the UDP Packet in a table style
* Displays the UDP Packet in a tabular style
* @param {Object} data
* @returns {html}
*/
present(data) {
const html = [];
html.push("<table class='table table-hover table-sm table-bordered table-nonfluid' style='table-layout: fixed'>");
html.push("<tr>");
html.push("<th>Field</th>");
html.push("<th>Value</th>");
html.push("</tr>");
for (const key in data) {
html.push("<tr>");
html.push("<td style=\"word-wrap:break-word\">" + key + "</td>");
html.push("<td>" + data[key] + "</td>");
html.push("</tr>");
}
html.push("</table>");
return html.join("");
return objToTable(data);
}
}

View File

@@ -20,12 +20,30 @@ class ProtobufDecode extends Operation {
super();
this.name = "Protobuf Decode";
this.module = "Default";
this.description = "Decodes any Protobuf encoded data to a JSON representation of the data using the field number as the field key.";
this.module = "Protobuf";
this.description = "Decodes any Protobuf encoded data to a JSON representation of the data using the field number as the field key.<br><br>If a .proto schema is defined, the encoded data will be decoded with reference to the schema. Only one message instance will be decoded. <br><br><u>Show Unknown Fields</u><br>When a schema is used, this option shows fields that are present in the input data but not defined in the schema.<br><br><u>Show Types</u><br>Show the type of a field next to its name. For undefined fields, the wiretype and example types are shown instead.";
this.infoURL = "https://wikipedia.org/wiki/Protocol_Buffers";
this.inputType = "ArrayBuffer";
this.outputType = "JSON";
this.args = [];
this.args = [
{
name: "Schema (.proto text)",
type: "text",
value: "",
rows: 8,
hint: "Drag and drop is enabled on this ingredient"
},
{
name: "Show Unknown Fields",
type: "boolean",
value: false
},
{
name: "Show Types",
type: "boolean",
value: false
}
];
}
/**
@@ -36,7 +54,7 @@ class ProtobufDecode extends Operation {
run(input, args) {
input = new Uint8Array(input);
try {
return Protobuf.decode(input);
return Protobuf.decode(input, args);
} catch (err) {
throw new OperationError(err);
}

View File

@@ -0,0 +1,54 @@
/**
* @author GCHQ Contributor [3]
* @copyright Crown Copyright 2021
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import Protobuf from "../lib/Protobuf.mjs";
/**
* Protobuf Encode operation
*/
class ProtobufEncode extends Operation {
/**
* ProtobufEncode constructor
*/
constructor() {
super();
this.name = "Protobuf Encode";
this.module = "Protobuf";
this.description = "Encodes a valid JSON object into a protobuf byte array using the input .proto schema.";
this.infoURL = "https://developers.google.com/protocol-buffers/docs/encoding";
this.inputType = "JSON";
this.outputType = "ArrayBuffer";
this.args = [
{
name: "Schema (.proto text)",
type: "text",
value: "",
rows: 8,
hint: "Drag and drop is enabled on this ingredient"
}
];
}
/**
* @param {Object} input
* @param {Object[]} args
* @returns {ArrayBuffer}
*/
run(input, args) {
try {
return Protobuf.encode(input, args);
} catch (error) {
throw new OperationError(error);
}
}
}
export default ProtobufEncode;

View File

@@ -43,9 +43,9 @@ class RC4Drop extends Operation {
"value": ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"]
},
{
"name": "Number of bytes to drop",
"name": "Number of dwords to drop",
"type": "number",
"value": 768
"value": 192
}
];
}

View File

@@ -0,0 +1,86 @@
/**
* @author Matt C [me@mitt.dev]
* @copyright Crown Copyright 2020
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import forge from "node-forge";
import { MD_ALGORITHMS } from "../lib/RSA.mjs";
/**
* RSA Decrypt operation
*/
class RSADecrypt extends Operation {
/**
* RSADecrypt constructor
*/
constructor() {
super();
this.name = "RSA Decrypt";
this.module = "Ciphers";
this.description = "Decrypt an RSA encrypted message with a PEM encoded private key.";
this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "RSA Private Key (PEM)",
type: "text",
value: "-----BEGIN RSA PRIVATE KEY-----"
},
{
name: "Key Password",
type: "text",
value: ""
},
{
name: "Encryption Scheme",
type: "argSelector",
value: [
{
name: "RSA-OAEP",
on: [3]
},
{
name: "RSAES-PKCS1-V1_5",
off: [3]
},
{
name: "RAW",
off: [3]
}]
},
{
name: "Message Digest Algorithm",
type: "option",
value: Object.keys(MD_ALGORITHMS)
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [pemKey, password, scheme, md] = args;
if (pemKey.replace("-----BEGIN RSA PRIVATE KEY-----", "").length === 0) {
throw new OperationError("Please enter a private key.");
}
try {
const privKey = forge.pki.decryptRsaPrivateKey(pemKey, password);
const dMsg = privKey.decrypt(input, scheme, {md: MD_ALGORITHMS[md].create()});
return forge.util.decodeUtf8(dMsg);
} catch (err) {
throw new OperationError(err);
}
}
}
export default RSADecrypt;

View File

@@ -0,0 +1,89 @@
/**
* @author Matt C [me@mitt.dev]
* @copyright Crown Copyright 2020
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import forge from "node-forge";
import { MD_ALGORITHMS } from "../lib/RSA.mjs";
/**
* RSA Encrypt operation
*/
class RSAEncrypt extends Operation {
/**
* RSAEncrypt constructor
*/
constructor() {
super();
this.name = "RSA Encrypt";
this.module = "Ciphers";
this.description = "Encrypt a message with a PEM encoded RSA public key.";
this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "RSA Public Key (PEM)",
type: "text",
value: "-----BEGIN RSA PUBLIC KEY-----"
},
{
name: "Encryption Scheme",
type: "argSelector",
value: [
{
name: "RSA-OAEP",
on: [2]
},
{
name: "RSAES-PKCS1-V1_5",
off: [2]
},
{
name: "RAW",
off: [2]
}]
},
{
name: "Message Digest Algorithm",
type: "option",
value: Object.keys(MD_ALGORITHMS)
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [pemKey, scheme, md] = args;
if (pemKey.replace("-----BEGIN RSA PUBLIC KEY-----", "").length === 0) {
throw new OperationError("Please enter a public key.");
}
try {
// Load public key
const pubKey = forge.pki.publicKeyFromPem(pemKey);
// https://github.com/digitalbazaar/forge/issues/465#issuecomment-271097600
const plaintextBytes = forge.util.encodeUtf8(input);
// Encrypt message
const eMsg = pubKey.encrypt(plaintextBytes, scheme, {md: MD_ALGORITHMS[md].create()});
return eMsg;
} catch (err) {
if (err.message === "RSAES-OAEP input message length is too long.") {
throw new OperationError(`RSAES-OAEP input message length (${err.length}) is longer than the maximum allowed length (${err.maxLength}).`);
}
throw new OperationError(err);
}
}
}
export default RSAEncrypt;

View File

@@ -0,0 +1,74 @@
/**
* @author Matt C [me@mitt.dev]
* @author gchq77703 []
* @copyright Crown Copyright 2020
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import forge from "node-forge";
import { MD_ALGORITHMS } from "../lib/RSA.mjs";
/**
* RSA Sign operation
*/
class RSASign extends Operation {
/**
* RSASign constructor
*/
constructor() {
super();
this.name = "RSA Sign";
this.module = "Ciphers";
this.description = "Sign a plaintext message with a PEM encoded RSA key.";
this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "RSA Private Key (PEM)",
type: "text",
value: "-----BEGIN RSA PRIVATE KEY-----"
},
{
name: "Key Password",
type: "text",
value: ""
},
{
name: "Message Digest Algorithm",
type: "option",
value: Object.keys(MD_ALGORITHMS)
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [key, password, mdAlgo] = args;
if (key.replace("-----BEGIN RSA PRIVATE KEY-----", "").length === 0) {
throw new OperationError("Please enter a private key.");
}
try {
const privateKey = forge.pki.decryptRsaPrivateKey(key, password);
// Generate message hash
const md = MD_ALGORITHMS[mdAlgo].create();
md.update(input, "utf8");
// Sign message hash
const sig = privateKey.sign(md);
return sig;
} catch (err) {
throw new OperationError(err);
}
}
}
export default RSASign;

View File

@@ -0,0 +1,77 @@
/**
* @author Matt C [me@mitt.dev]
* @copyright Crown Copyright 2020
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import forge from "node-forge";
import { MD_ALGORITHMS } from "../lib/RSA.mjs";
/**
* RSA Verify operation
*/
class RSAVerify extends Operation {
/**
* RSAVerify constructor
*/
constructor() {
super();
this.name = "RSA Verify";
this.module = "Ciphers";
this.description = "Verify a message against a signature and a public PEM encoded RSA key.";
this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "RSA Public Key (PEM)",
type: "text",
value: "-----BEGIN RSA PUBLIC KEY-----"
},
{
name: "Message",
type: "text",
value: ""
},
{
name: "Message Digest Algorithm",
type: "option",
value: Object.keys(MD_ALGORITHMS)
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [pemKey, message, mdAlgo] = args;
if (pemKey.replace("-----BEGIN RSA PUBLIC KEY-----", "").length === 0) {
throw new OperationError("Please enter a public key.");
}
try {
// Load public key
const pubKey = forge.pki.publicKeyFromPem(pemKey);
// Generate message digest
const md = MD_ALGORITHMS[mdAlgo].create();
md.update(message, "utf8");
// Compare signed message digest and generated message digest
const result = pubKey.verify(md.digest().bytes(), input);
return result ? "Verified OK" : "Verification Failure";
} catch (err) {
if (err.message === "Encrypted message length is invalid.") {
throw new OperationError(`Signature length (${err.length}) does not match expected length based on key (${err.expected}).`);
}
throw new OperationError(err);
}
}
}
export default RSAVerify;

Some files were not shown because too many files have changed in this diff Show More