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

Compare commits

..

132 Commits

Author SHA1 Message Date
n1474335
7197a434c2 9.38.8 2022-06-08 18:07:32 +01:00
n1474335
5349115b94 'JSON Beautify' operation now supports formatting, collapsing and syntax highlighting. Closes #203. 2022-06-08 18:07:11 +01:00
n1474335
4274e8f3a2 Fixed PEM wiki link 2022-06-06 15:20:31 +01:00
n1474335
7610e159a3 9.38.7 2022-06-06 14:54:27 +01:00
n1474335
9ec94434bb Fixed 'From Base64' bug adding null bytes. Closes #1362 2022-06-06 14:54:06 +01:00
n1474335
3f57711c39 9.38.6 2022-06-03 22:58:23 +01:00
n1474335
dc46018757 Tidied up 'PEM to Hex' operation 2022-06-03 22:57:49 +01:00
n1474335
1464e5d5e4 Merge branch 'pem' of https://github.com/cplussharp/CyberChef 2022-06-03 22:21:37 +01:00
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
CPlusSharp
b7a978505f PEMToHex: add magic check so it gets found 2021-11-17 20:19:42 +01: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
CPlusSharp
1dbcd2ac84 PEMtoHex: Support arbitrary PEMs
previous implementation only supported some PEMs (e.g. Certificate)

the new implementation is more general,
it just extracts the base64 between
header and footer and decodes that to hex
2021-11-07 11:21:17 +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
Gitoffthelawn
7712ee7f35 Improved consistency
Changed "or" to "and" because all the other sentence sections use "and".
2021-08-11 01:55:47 -07: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
n1073645
c01ce90e06 Tests Added 2020-07-06 11:20:54 +01:00
n1073645
d68c8cb845 Casing Variations 2020-07-06 10:44:05 +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
115 changed files with 24756 additions and 9190 deletions

View File

@@ -1,5 +1,5 @@
{
"parser": "babel-eslint",
"parser": "@babel/eslint-parser",
"parserOptions": {
"ecmaVersion": 9,
"ecmaFeatures": {

View File

@@ -1,6 +1,7 @@
name: "CodeQL Analysis"
on:
workflow_dispatch:
push:
branches: [ master ]
pull_request:

View File

@@ -1,6 +1,7 @@
name: "Master Build, Test & Deploy"
on:
workflow_dispatch:
push:
branches:
- master
@@ -14,7 +15,7 @@ jobs:
- name: Set node version
uses: actions/setup-node@v1
with:
node-version: '10.x'
node-version: '17.x'
- name: Install
run: |
@@ -27,7 +28,7 @@ jobs:
- name: Unit Tests
run: |
npm test
npx grunt testnodeconsumer
npm run testnodeconsumer
- name: Production Build
if: success()

View File

@@ -1,6 +1,7 @@
name: "Pull Requests"
on:
workflow_dispatch:
pull_request:
types: [synchronize, opened, reopened]
@@ -13,7 +14,7 @@ jobs:
- name: Set node version
uses: actions/setup-node@v1
with:
node-version: '10.x'
node-version: '17.x'
- name: Install
run: |
@@ -26,7 +27,7 @@ jobs:
- name: Unit Tests
run: |
npm test
npx grunt testnodeconsumer
npm run testnodeconsumer
- name: Production Build
if: success()

View File

@@ -1,6 +1,7 @@
name: "Releases"
on:
workflow_dispatch:
push:
tags:
- 'v*'
@@ -14,7 +15,7 @@ jobs:
- name: Set node version
uses: actions/setup-node@v1
with:
node-version: '10.x'
node-version: '17.x'
- name: Install
run: |
@@ -27,7 +28,7 @@ jobs:
- name: Unit Tests
run: |
npm test
npx grunt testnodeconsumer
npm run testnodeconsumer
- name: Production Build
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

2
.nvmrc
View File

@@ -1 +1 @@
lts/dubnium
17

View File

@@ -13,6 +13,26 @@ All major and minor version changes will be documented in this file. Details of
## 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]
@@ -118,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]
@@ -271,6 +291,12 @@ 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
@@ -384,12 +410,17 @@ All major and minor version changes will be documented in this file. Details of
[@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
@@ -458,6 +489,7 @@ All major and minor version changes will be documented in this file. Details of
[#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
@@ -466,6 +498,13 @@ All major and minor version changes will be documented in this file. Details of
[#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
[#1083]: https://github.com/gchq/CyberChef/pull/1083
[#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",
@@ -187,9 +189,6 @@ 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/**/*"],
@@ -201,50 +200,36 @@ module.exports = function (grunt) {
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: {
@@ -349,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
@@ -365,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
@@ -393,21 +378,14 @@ 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,
},

View File

@@ -2,7 +2,6 @@
[![](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)
[![dependencies Status](https://david-dm.org/gchq/CyberChef/status.svg)](https://david-dm.org/gchq/CyberChef)
[![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)
@@ -10,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.
@@ -106,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": {

28860
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "cyberchef",
"version": "9.32.0",
"version": "9.38.8",
"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,155 +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.16",
"@babel/plugin-transform-runtime": "^7.12.15",
"@babel/preset-env": "^7.12.16",
"autoprefixer": "^10.2.4",
"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": "^92.0.0",
"cli-progress": "^3.9.0",
"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.2",
"eslint": "^7.20.0",
"exports-loader": "^2.0.0",
"file-loader": "^6.2.0",
"grunt": "^1.3.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": "^5.1.0",
"imports-loader": "^2.0.0",
"mini-css-extract-plugin": "^1.3.7",
"nightwatch": "^1.7.7",
"node-sass": "^5.0.0",
"postcss": "^8.2.6",
"postcss-css-variables": "^0.17.0",
"postcss-import": "^14.0.0",
"postcss-loader": "^5.0.0",
"prompt": "^1.1.0",
"sass-loader": "^11.0.1",
"sitemap": "^6.3.6",
"style-loader": "^2.0.0",
"svg-url-loader": "^7.1.1",
"url-loader": "^4.1.1",
"webpack": "^5.22.0",
"webpack-bundle-analyzer": "^4.4.0",
"webpack-dev-server": "^3.11.2",
"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.13",
"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.6.0",
"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": "^5.0.1",
"cbor": "8.1.0",
"chi-squared": "^1.1.0",
"codepage": "^1.14.0",
"core-js": "^3.8.3",
"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.5.0",
"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",
"flat": "^5.0.2",
"geodesy": "1.1.3",
"highlight.js": "^10.6.0",
"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",
"json5": "^2.2.1",
"jsonpath": "^1.1.1",
"jsonwebtoken": "^8.5.1",
"jsqr": "^1.3.1",
"jsrsasign": "^10.3.0",
"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.21",
"loglevel": "^1.7.1",
"loglevel": "^1.8.0",
"loglevel-message-prefix": "^3.0.0",
"markdown-it": "^12.0.4",
"moment": "^2.29.1",
"moment-timezone": "^0.5.33",
"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",
"path": "^0.12.7",
"popper.js": "^1.16.1",
"process": "^0.11.10",
"protobufjs": "^6.11.2",
"protobufjs": "^6.11.3",
"qr-image": "^3.2.0",
"scryptsy": "^2.1.0",
"snackbarjs": "^1.1.0",
"sortablejs": "^1.13.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.6.0",
"tesseract.js": "2.1.1",
"ua-parser-js": "^0.7.24",
"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.6.0",
"xpath": "0.0.32",
"xregexp": "^5.0.1",
"xregexp": "^5.1.0",
"zlibjs": "^0.3.1"
},
"scripts": {
"start": "npx grunt dev",
"build": "npx grunt prod",
"repl": "node src/node/repl.js",
"test": "npx 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": "npx grunt testnodeconsumer",
"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": "npx grunt lint",
"postinstall": "npx grunt exec:fixCryptoApiImports",
"newop": "node --experimental-modules src/core/config/scripts/newOperation.mjs",
"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

@@ -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

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";

View File

@@ -723,7 +723,8 @@ class Utils {
}
if (removeScriptAndStyle) {
htmlStr = recursiveRemove(/<(script|style)[^>]*>.*?<\/(script|style)>/gi, htmlStr);
htmlStr = recursiveRemove(/<script[^>]*>.*?<\/script[^>]*>/gi, htmlStr);
htmlStr = recursiveRemove(/<style[^>]*>.*?<\/style[^>]*>/gi, htmlStr);
}
return htmlStr.replace(/<[^>]+>/g, "");
}
@@ -1206,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;
}
}
}
}
/**

View File

@@ -20,6 +20,8 @@
"From Octal",
"To Base32",
"From Base32",
"To Base45",
"From Base45",
"To Base58",
"From Base58",
"To Base62",
@@ -81,6 +83,8 @@
"RC2 Decrypt",
"RC4",
"RC4 Drop",
"SM4 Encrypt",
"SM4 Decrypt",
"ROT13",
"ROT47",
"XOR",
@@ -118,7 +122,8 @@
"Multiple Bombe",
"Typex",
"Lorenz",
"Colossus"
"Colossus",
"SIGABA"
]
},
{
@@ -185,6 +190,7 @@
"Parse IP range",
"Parse IPv6 address",
"Parse IPv4 header",
"Parse TCP",
"Parse UDP",
"Parse SSH Host Key",
"Parse URI",
@@ -230,6 +236,7 @@
"From Case Insensitive Regex",
"Add line numbers",
"Remove line numbers",
"Get All Casings",
"To Table",
"Reverse",
"Sort",

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

@@ -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,28 @@ 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++) || "=");
// Including `|| null` forces empty strings to null so that indexOf returns -1 instead of 0
enc1 = alphabet.indexOf(data.charAt(i++) || null);
enc2 = alphabet.indexOf(data.charAt(i++) || null);
enc3 = alphabet.indexOf(data.charAt(i++) || null);
enc4 = alphabet.indexOf(data.charAt(i++) || null);
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 >= 0 && chr1 < 256) {
output.push(chr1);
}
if (chr2 >= 0 && chr2 < 256 && enc3 !== 64) {
output.push(chr2);
}
if (enc4 !== 64) {
if (chr3 >= 0 && chr3 < 256 && enc4 !== 64) {
output.push(chr3);
}
}

View File

@@ -13,33 +13,39 @@ 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;
}
@@ -53,10 +59,10 @@ 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)

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
@@ -128,7 +129,7 @@ export function getScatterValuesWithColour(input, recordDelimiter, fieldDelimite
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 };

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

@@ -3778,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 */
@@ -3798,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
@@ -3819,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;
@@ -3886,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");
}
}
@@ -3948,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

@@ -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

@@ -141,7 +141,7 @@ class Protobuf {
const messageNames = [];
const fieldTypes = [];
this.parsedProto.root.nestedArray.forEach(block => {
if (block.constructor.name === "Type") {
if (block instanceof protobuf.Type) {
messageNames.push(block.name);
this.parsedProto.root.nested[block.name].fieldsArray.forEach(field => {
fieldTypes.push(field.type);
@@ -152,12 +152,12 @@ class Protobuf {
if (messageNames.length === 0) {
this.mainMessageName = null;
} else {
for (const name of messageNames) {
if (!fieldTypes.includes(name)) {
this.mainMessageName = name;
break;
}
}
// for (const name of messageNames) {
// if (!fieldTypes.includes(name)) {
// this.mainMessageName = name;
// break;
// }
// }
this.mainMessageName = messageNames[0];
}
}
@@ -213,7 +213,7 @@ class Protobuf {
*/
static appendTypesToFieldNames(schemaRoot) {
for (const block of schemaRoot.nestedArray) {
if (block.constructor.name === "Type") {
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));
@@ -306,7 +306,7 @@ class Protobuf {
}
// Check for submessage fields
if (schemaField.resolvedType !== null && schemaField.resolvedType.constructor.name === "Type") {
if (schemaField.resolvedType instanceof protobuf.Type) {
const subMessageType = schemaMessage.fields[schemaFieldName].type;
const schemaSubMessage = this.parsedProto.root.nested[subMessageType];
const rawSubMessages = rawDecodedMessage[fieldName];

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();
}

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);
}
}

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";
@@ -66,6 +66,14 @@ class AESDecrypt extends Operation {
{
name: "ECB",
off: [5, 6]
},
{
name: "CBC/NoPadding",
off: [5, 6]
},
{
name: "ECB/NoPadding",
off: [5, 6]
}
]
},
@@ -104,7 +112,8 @@ 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),
@@ -122,6 +131,14 @@ 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: mode === "GCM" ? gcmTag : undefined,

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

@@ -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

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": true
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],
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;
return search(input, regex, null, displayTotal);
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

@@ -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

@@ -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,93 +34,98 @@ 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]
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]
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]
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]
args: ["HNO4klm6ij9n+J2hyf0gzA8uvwDEq3X1Q7ZKeFrWcVTts/MRGYbdxSo=ILaUpPBC5", true, false]
}
];
}
@@ -131,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

@@ -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

@@ -5,8 +5,10 @@
* @license Apache-2.0
*/
import vkbeautify from "vkbeautify";
import JSON5 from "json5";
import OperationError from "../errors/OperationError.mjs";
import Operation from "../Operation.mjs";
import Utils from "../Utils.mjs";
/**
* JSON Beautify operation
@@ -21,19 +23,25 @@ class JSONBeautify extends Operation {
this.name = "JSON Beautify";
this.module = "Code";
this.description = "Indents and prettifies JavaScript Object Notation (JSON) code.";
this.description = "Indents and pretty prints JavaScript Object Notation (JSON) code.<br><br>Tags: json viewer, prettify, syntax highlighting";
this.inputType = "string";
this.outputType = "string";
this.presentType = "html";
this.args = [
{
"name": "Indent string",
"type": "binaryShortString",
"value": " "
name: "Indent string",
type: "binaryShortString",
value: " "
},
{
"name": "Sort Object Keys",
"type": "boolean",
"value": false
name: "Sort Object Keys",
type: "boolean",
value: false
},
{
name: "Formatted",
type: "boolean",
value: true
}
];
}
@@ -44,35 +52,193 @@ class JSONBeautify extends Operation {
* @returns {string}
*/
run(input, args) {
const [indentStr, sortBool] = args;
if (!input) return "";
if (sortBool) {
input = JSON.stringify(JSONBeautify._sort(JSON.parse(input)));
const [indentStr, sortBool] = args;
let json = null;
try {
json = JSON5.parse(input);
} catch (err) {
throw new OperationError("Unable to parse input as JSON.\n" + err);
}
return vkbeautify.json(input, indentStr);
if (sortBool) json = sortKeys(json);
return JSON.stringify(json, null, indentStr);
}
/**
* Sort JSON representation of an object
* Adds various dynamic features to the JSON blob
*
* @author Phillip Nordwall [phillip.nordwall@gmail.com]
* @private
* @param {object} o
* @returns {object}
* @param {string} data
* @param {Object[]} args
* @returns {html}
*/
static _sort(o) {
if (Array.isArray(o)) {
return o.map(JSONBeautify._sort);
} else if ("[object Object]" === Object.prototype.toString.call(o)) {
return Object.keys(o).sort().reduce(function(a, k) {
a[k] = JSONBeautify._sort(o[k]);
return a;
}, {});
present(data, args) {
const formatted = args[2];
if (!formatted) return Utils.escapeHtml(data);
const json = JSON5.parse(data);
const options = {
withLinks: true,
bigNumbers: true
};
let html = '<div class="json-document">';
if (isCollapsable(json)) {
const isArr = json instanceof Array;
html += '<details open class="json-details">' +
`<summary class="json-summary ${isArr ? "json-arr" : "json-obj"}"></summary>` +
json2html(json, options) +
"</details>";
} else {
html += json2html(json, options);
}
return o;
html += "</div>";
return html;
}
}
/**
* Sort keys in a JSON object
*
* @author Phillip Nordwall [phillip.nordwall@gmail.com]
* @param {object} o
* @returns {object}
*/
function sortKeys(o) {
if (Array.isArray(o)) {
return o.map(sortKeys);
} else if ("[object Object]" === Object.prototype.toString.call(o)) {
return Object.keys(o).sort().reduce(function(a, k) {
a[k] = sortKeys(o[k]);
return a;
}, {});
}
return o;
}
/**
* Check if arg is either an array with at least 1 element, or a dict with at least 1 key
* @returns {boolean}
*/
function isCollapsable(arg) {
return arg instanceof Object && Object.keys(arg).length > 0;
}
/**
* Check if a string looks like a URL, based on protocol
* @returns {boolean}
*/
function isUrl(string) {
const protocols = ["http", "https", "ftp", "ftps"];
for (let i = 0; i < protocols.length; i++) {
if (string.startsWith(protocols[i] + "://")) {
return true;
}
}
return false;
}
/**
* Transform a json object into html representation
*
* Adapted for CyberChef by @n1474335 from jQuery json-viewer
* @author Alexandre Bodelot <alexandre.bodelot@gmail.com>
* @link https://github.com/abodelot/jquery.json-viewer
* @license MIT
*
* @returns {string}
*/
function json2html(json, options) {
let html = "";
if (typeof json === "string") {
// Escape tags and quotes
json = Utils.escapeHtml(json);
if (options.withLinks && isUrl(json)) {
html += `<a href="${json}" class="json-string" target="_blank">${json}</a>`;
} else {
// Escape double quotes in the rendered non-URL string.
json = json.replace(/&quot;/g, "\\&quot;");
html += `<span class="json-string">"${json}"</span>`;
}
} else if (typeof json === "number" || typeof json === "bigint") {
html += `<span class="json-literal">${json}</span>`;
} else if (typeof json === "boolean") {
html += `<span class="json-literal">${json}</span>`;
} else if (json === null) {
html += '<span class="json-literal">null</span>';
} else if (json instanceof Array) {
if (json.length > 0) {
html += '<span class="json-bracket">[</span><ol class="json-array">';
for (let i = 0; i < json.length; i++) {
html += "<li>";
// Add toggle button if item is collapsable
if (isCollapsable(json[i])) {
const isArr = json[i] instanceof Array;
html += '<details open class="json-details">' +
`<summary class="json-summary ${isArr ? "json-arr" : "json-obj"}"></summary>` +
json2html(json[i], options) +
"</details>";
} else {
html += json2html(json[i], options);
}
// Add comma if item is not last
if (i < json.length - 1) {
html += '<span class="json-comma">,</span>';
}
html += "</li>";
}
html += '</ol><span class="json-bracket">]</span>';
} else {
html += '<span class="json-bracket">[]</span>';
}
} else if (typeof json === "object") {
// Optional support different libraries for big numbers
// json.isLosslessNumber: package lossless-json
// json.toExponential(): packages bignumber.js, big.js, decimal.js, decimal.js-light, others?
if (options.bigNumbers && (typeof json.toExponential === "function" || json.isLosslessNumber)) {
html += `<span class="json-literal">${json.toString()}</span>`;
} else {
let keyCount = Object.keys(json).length;
if (keyCount > 0) {
html += '<span class="json-brace">{</span><ul class="json-dict">';
for (const key in json) {
if (Object.prototype.hasOwnProperty.call(json, key)) {
const safeKey = Utils.escapeHtml(key);
html += "<li>";
// Add toggle button if item is collapsable
if (isCollapsable(json[key])) {
const isArr = json[key] instanceof Array;
html += '<details open class="json-details">' +
`<summary class="json-summary ${isArr ? "json-arr" : "json-obj"}">${safeKey}<span class="json-colon">:</span> </summary>` +
json2html(json[key], options) +
"</details>";
} else {
html += safeKey + '<span class="json-colon">:</span> ' + json2html(json[key], options);
}
// Add comma if item is not last
if (--keyCount > 0) {
html += '<span class="json-comma">,</span>';
}
html += "</li>";
}
}
html += '</ul><span class="json-brace">}</span>';
} else {
html += '<span class="json-brace">{}</span>';
}
}
}
return html;
}
export default JSONBeautify;

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

View File

@@ -1,11 +1,14 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @author cplussharp
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import r from "jsrsasign";
import { fromBase64 } from "../lib/Base64.mjs";
import { toHexFast } from "../lib/Hex.mjs";
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
/**
* PEM to Hex operation
@@ -19,12 +22,18 @@ class PEMToHex extends Operation {
super();
this.name = "PEM to Hex";
this.module = "PublicKey";
this.module = "Default";
this.description = "Converts PEM (Privacy Enhanced Mail) format to a hexadecimal DER (Distinguished Encoding Rules) string.";
this.infoURL = "https://wikipedia.org/wiki/X.690#DER_encoding";
this.infoURL = "https://wikipedia.org/wiki/Privacy-Enhanced_Mail#Format";
this.inputType = "string";
this.outputType = "string";
this.args = [];
this.checks = [
{
"pattern": "----BEGIN ([A-Z][A-Z ]+[A-Z])-----",
"args": []
}
];
}
/**
@@ -33,17 +42,25 @@ class PEMToHex extends Operation {
* @returns {string}
*/
run(input, args) {
if (input.indexOf("-----BEGIN") < 0) {
// Add header so that the KEYUTIL function works
input = "-----BEGIN CERTIFICATE-----" + input;
const output = [];
let match;
const regex = /-----BEGIN ([A-Z][A-Z ]+[A-Z])-----/g;
while ((match = regex.exec(input)) !== null) {
// find corresponding end tag
const indexBase64 = match.index + match[0].length;
const footer = `-----END ${match[1]}-----`;
const indexFooter = input.indexOf(footer, indexBase64);
if (indexFooter === -1) {
throw new OperationError(`PEM footer '${footer}' not found`);
}
// decode base64 content
const base64 = input.substring(indexBase64, indexFooter);
const bytes = fromBase64(base64, "A-Za-z0-9+/=", "byteArray", true);
const hex = toHexFast(bytes);
output.push(hex);
}
if (input.indexOf("-----END") < 0) {
// Add footer so that the KEYUTIL function works
input = input + "-----END CERTIFICATE-----";
}
const cert = new r.X509();
cert.readCertPEM(input);
return cert.hex;
return output.join("\n");
}
}

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

@@ -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,287 @@
/**
* Emulation of the SIGABA machine.
*
* @author hettysymes
* @copyright hettysymes 2020
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import {LETTERS} from "../lib/Enigma.mjs";
import {NUMBERS, CR_ROTORS, I_ROTORS, SigabaMachine, CRRotor, IRotor} from "../lib/SIGABA.mjs";
/**
* Sigaba operation
*/
class Sigaba extends Operation {
/**
* Sigaba constructor
*/
constructor() {
super();
this.name = "SIGABA";
this.module = "Bletchley";
this.description = "Encipher/decipher with the WW2 SIGABA machine. <br><br>SIGABA, otherwise known as ECM Mark II, was used by the United States for message encryption during WW2 up to the 1950s. It was developed in the 1930s by the US Army and Navy, and has up to this day never been broken. Consisting of 15 rotors: 5 cipher rotors and 10 rotors (5 control rotors and 5 index rotors) controlling the stepping of the cipher rotors, the rotor stepping for SIGABA is much more complex than other rotor machines of its time, such as Enigma. All example rotor wirings are random example sets.<br><br>To configure rotor wirings, for the cipher and control rotors enter a string of letters which map from A to Z, and for the index rotors enter a sequence of numbers which map from 0 to 9. Note that encryption is not the same as decryption, so first choose the desired mode. <br><br> Note: Whilst this has been tested against other software emulators, it has not been tested against hardware.";
this.infoURL = "https://wikipedia.org/wiki/SIGABA";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "1st (left-hand) cipher rotor",
type: "editableOption",
value: CR_ROTORS,
defaultIndex: 0
},
{
name: "1st cipher rotor reversed",
type: "boolean",
value: false
},
{
name: "1st cipher rotor intial value",
type: "option",
value: LETTERS
},
{
name: "2nd cipher rotor",
type: "editableOption",
value: CR_ROTORS,
defaultIndex: 0
},
{
name: "2nd cipher rotor reversed",
type: "boolean",
value: false
},
{
name: "2nd cipher rotor intial value",
type: "option",
value: LETTERS
},
{
name: "3rd (middle) cipher rotor",
type: "editableOption",
value: CR_ROTORS,
defaultIndex: 0
},
{
name: "3rd cipher rotor reversed",
type: "boolean",
value: false
},
{
name: "3rd cipher rotor intial value",
type: "option",
value: LETTERS
},
{
name: "4th cipher rotor",
type: "editableOption",
value: CR_ROTORS,
defaultIndex: 0
},
{
name: "4th cipher rotor reversed",
type: "boolean",
value: false
},
{
name: "4th cipher rotor intial value",
type: "option",
value: LETTERS
},
{
name: "5th (right-hand) cipher rotor",
type: "editableOption",
value: CR_ROTORS,
defaultIndex: 0
},
{
name: "5th cipher rotor reversed",
type: "boolean",
value: false
},
{
name: "5th cipher rotor intial value",
type: "option",
value: LETTERS
},
{
name: "1st (left-hand) control rotor",
type: "editableOption",
value: CR_ROTORS,
defaultIndex: 0
},
{
name: "1st control rotor reversed",
type: "boolean",
value: false
},
{
name: "1st control rotor intial value",
type: "option",
value: LETTERS
},
{
name: "2nd control rotor",
type: "editableOption",
value: CR_ROTORS,
defaultIndex: 0
},
{
name: "2nd control rotor reversed",
type: "boolean",
value: false
},
{
name: "2nd control rotor intial value",
type: "option",
value: LETTERS
},
{
name: "3rd (middle) control rotor",
type: "editableOption",
value: CR_ROTORS,
defaultIndex: 0
},
{
name: "3rd control rotor reversed",
type: "boolean",
value: false
},
{
name: "3rd control rotor intial value",
type: "option",
value: LETTERS
},
{
name: "4th control rotor",
type: "editableOption",
value: CR_ROTORS,
defaultIndex: 0
},
{
name: "4th control rotor reversed",
type: "boolean",
value: false
},
{
name: "4th control rotor intial value",
type: "option",
value: LETTERS
},
{
name: "5th (right-hand) control rotor",
type: "editableOption",
value: CR_ROTORS,
defaultIndex: 0
},
{
name: "5th control rotor reversed",
type: "boolean",
value: false
},
{
name: "5th control rotor intial value",
type: "option",
value: LETTERS
},
{
name: "1st (left-hand) index rotor",
type: "editableOption",
value: I_ROTORS,
defaultIndex: 0
},
{
name: "1st index rotor intial value",
type: "option",
value: NUMBERS
},
{
name: "2nd index rotor",
type: "editableOption",
value: I_ROTORS,
defaultIndex: 0
},
{
name: "2nd index rotor intial value",
type: "option",
value: NUMBERS
},
{
name: "3rd (middle) index rotor",
type: "editableOption",
value: I_ROTORS,
defaultIndex: 0
},
{
name: "3rd index rotor intial value",
type: "option",
value: NUMBERS
},
{
name: "4th index rotor",
type: "editableOption",
value: I_ROTORS,
defaultIndex: 0
},
{
name: "4th index rotor intial value",
type: "option",
value: NUMBERS
},
{
name: "5th (right-hand) index rotor",
type: "editableOption",
value: I_ROTORS,
defaultIndex: 0
},
{
name: "5th index rotor intial value",
type: "option",
value: NUMBERS
},
{
name: "SIGABA mode",
type: "option",
value: ["Encrypt", "Decrypt"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const sigabaSwitch = args[40];
const cipherRotors = [];
const controlRotors = [];
const indexRotors = [];
for (let i=0; i<5; i++) {
const rotorWiring = args[i*3];
cipherRotors.push(new CRRotor(rotorWiring, args[i*3+2], args[i*3+1]));
}
for (let i=5; i<10; i++) {
const rotorWiring = args[i*3];
controlRotors.push(new CRRotor(rotorWiring, args[i*3+2], args[i*3+1]));
}
for (let i=15; i<20; i++) {
const rotorWiring = args[i*2];
indexRotors.push(new IRotor(rotorWiring, args[i*2+1]));
}
const sigaba = new SigabaMachine(cipherRotors, controlRotors, indexRotors);
let result;
if (sigabaSwitch === "Encrypt") {
result = sigaba.encrypt(input);
} else if (sigabaSwitch === "Decrypt") {
result = sigaba.decrypt(input);
}
return result;
}
}
export default Sigaba;

View File

@@ -0,0 +1,88 @@
/**
* @author swesven
* @copyright 2021
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import Utils from "../Utils.mjs";
import OperationError from "../errors/OperationError.mjs";
import { toHex } from "../lib/Hex.mjs";
import { decryptSM4 } from "../lib/SM4.mjs";
/**
* SM4 Decrypt operation
*/
class SM4Decrypt extends Operation {
/**
* SM4Encrypt constructor
*/
constructor() {
super();
this.name = "SM4 Decrypt";
this.module = "Ciphers";
this.description = "SM4 is a 128-bit block cipher, currently established as a national standard (GB/T 32907-2016) of China.";
this.infoURL = "https://wikipedia.org/wiki/SM4_(cipher)";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Key",
"type": "toggleString",
"value": "",
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
},
{
"name": "IV",
"type": "toggleString",
"value": "",
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
},
{
"name": "Mode",
"type": "option",
"value": ["CBC", "CFB", "OFB", "CTR", "ECB", "CBC/NoPadding", "ECB/NoPadding"]
},
{
"name": "Input",
"type": "option",
"value": ["Raw", "Hex"]
},
{
"name": "Output",
"type": "option",
"value": ["Hex", "Raw"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const key = Utils.convertToByteArray(args[0].string, args[0].option),
iv = Utils.convertToByteArray(args[1].string, args[1].option),
[,, mode, inputType, outputType] = args;
if (key.length !== 16)
throw new OperationError(`Invalid key length: ${key.length} bytes
SM4 uses a key length of 16 bytes (128 bits).`);
if (iv.length !== 16 && !mode.startsWith("ECB"))
throw new OperationError(`Invalid IV length: ${iv.length} bytes
SM4 uses an IV length of 16 bytes (128 bits).
Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
input = Utils.convertToByteArray(input, inputType);
const output = decryptSM4(input, key, iv, mode.substring(0, 3), mode.endsWith("NoPadding"));
return outputType === "Hex" ? toHex(output) : Utils.byteArrayToUtf8(output);
}
}
export default SM4Decrypt;

View File

@@ -0,0 +1,88 @@
/**
* @author swesven
* @copyright 2021
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import Utils from "../Utils.mjs";
import OperationError from "../errors/OperationError.mjs";
import { toHex } from "../lib/Hex.mjs";
import { encryptSM4 } from "../lib/SM4.mjs";
/**
* SM4 Encrypt operation
*/
class SM4Encrypt extends Operation {
/**
* SM4Encrypt constructor
*/
constructor() {
super();
this.name = "SM4 Encrypt";
this.module = "Ciphers";
this.description = "SM4 is a 128-bit block cipher, currently established as a national standard (GB/T 32907-2016) of China. Multiple block cipher modes are supported. When using CBC or ECB mode, the PKCS#7 padding scheme is used.";
this.infoURL = "https://wikipedia.org/wiki/SM4_(cipher)";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Key",
"type": "toggleString",
"value": "",
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
},
{
"name": "IV",
"type": "toggleString",
"value": "",
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
},
{
"name": "Mode",
"type": "option",
"value": ["CBC", "CFB", "OFB", "CTR", "ECB"]
},
{
"name": "Input",
"type": "option",
"value": ["Raw", "Hex"]
},
{
"name": "Output",
"type": "option",
"value": ["Hex", "Raw"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const key = Utils.convertToByteArray(args[0].string, args[0].option),
iv = Utils.convertToByteArray(args[1].string, args[1].option),
[,, mode, inputType, outputType] = args;
if (key.length !== 16)
throw new OperationError(`Invalid key length: ${key.length} bytes
SM4 uses a key length of 16 bytes (128 bits).`);
if (iv.length !== 16 && !mode.startsWith("ECB"))
throw new OperationError(`Invalid IV length: ${iv.length} bytes
SM4 uses an IV length of 16 bytes (128 bits).
Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
input = Utils.convertToByteArray(input, inputType);
const output = encryptSM4(input, key, iv, mode.substring(0, 3), mode.endsWith("NoPadding"));
return outputType === "Hex" ? toHex(output) : Utils.byteArrayToUtf8(output);
}
}
export default SM4Encrypt;

View File

@@ -87,7 +87,7 @@ class ScatterChart extends Operation {
const recordDelimiter = Utils.charRep(args[0]),
fieldDelimiter = Utils.charRep(args[1]),
columnHeadingsAreIncluded = args[2],
fillColour = args[5],
fillColour = Utils.escapeHtml(args[5]),
radius = args[6],
colourInInput = args[7],
dimension = 500;

View File

@@ -72,7 +72,10 @@ class SeriesChart extends Operation {
fieldDelimiter = Utils.charRep(args[1]),
xLabel = args[2],
pipRadius = args[3],
seriesColours = args[4].split(","),
// Escape HTML from all colours to prevent reflected XSS. See https://github.com/gchq/CyberChef/issues/1265
seriesColours = args[4].split(",").map((colour) => {
return Utils.escapeHtml(colour);
}),
svgWidth = 500,
interSeriesPadding = 20,
xAxisHeight = 50,

View File

@@ -7,6 +7,7 @@
import Operation from "../Operation.mjs";
import Utils from "../Utils.mjs";
import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs";
import {caseInsensitiveSort, ipSort, numericSort, hexadecimalSort} from "../lib/Sort.mjs";
/**
* Sort operation
@@ -57,120 +58,19 @@ class Sort extends Operation {
if (order === "Alphabetical (case sensitive)") {
sorted = sorted.sort();
} else if (order === "Alphabetical (case insensitive)") {
sorted = sorted.sort(Sort._caseInsensitiveSort);
sorted = sorted.sort(caseInsensitiveSort);
} else if (order === "IP address") {
sorted = sorted.sort(Sort._ipSort);
sorted = sorted.sort(ipSort);
} else if (order === "Numeric") {
sorted = sorted.sort(Sort._numericSort);
sorted = sorted.sort(numericSort);
} else if (order === "Numeric (hexadecimal)") {
sorted = sorted.sort(Sort._hexadecimalSort);
sorted = sorted.sort(hexadecimalSort);
}
if (sortReverse) sorted.reverse();
return sorted.join(delim);
}
/**
* Comparison operation for sorting of strings ignoring case.
*
* @private
* @param {string} a
* @param {string} b
* @returns {number}
*/
static _caseInsensitiveSort(a, b) {
return a.toLowerCase().localeCompare(b.toLowerCase());
}
/**
* Comparison operation for sorting of IPv4 addresses.
*
* @private
* @param {string} a
* @param {string} b
* @returns {number}
*/
static _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
* @private
* @param {string} a
* @param {string} b
* @returns {number}
*/
static _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
* @private
* @param {string} a
* @param {string} b
* @returns {number}
*/
static _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);
}
}
export default Sort;

View File

@@ -7,6 +7,7 @@
import Operation from "../Operation.mjs";
import XRegExp from "xregexp";
import { search } from "../lib/Extract.mjs";
import { caseInsensitiveSort } from "../lib/Sort.mjs";
/**
* Strings operation
@@ -27,27 +28,37 @@ class Strings extends Operation {
this.outputType = "string";
this.args = [
{
"name": "Encoding",
"type": "option",
"value": ["Single byte", "16-bit littleendian", "16-bit bigendian", "All"]
name: "Encoding",
type: "option",
value: ["Single byte", "16-bit littleendian", "16-bit bigendian", "All"]
},
{
"name": "Minimum length",
"type": "number",
"value": 4
name: "Minimum length",
type: "number",
value: 4
},
{
"name": "Match",
"type": "option",
"value": [
name: "Match",
type: "option",
value: [
"[ASCII]", "Alphanumeric + punctuation (A)", "All printable chars (A)", "Null-terminated strings (A)",
"[Unicode]", "Alphanumeric + punctuation (U)", "All printable chars (U)", "Null-terminated strings (U)"
]
},
{
"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
}
];
}
@@ -58,7 +69,7 @@ class Strings extends Operation {
* @returns {string}
*/
run(input, args) {
const [encoding, minLen, matchType, displayTotal] = args,
const [encoding, minLen, matchType, displayTotal, sort, unique] = args,
alphanumeric = "A-Z\\d",
punctuation = "/\\-:.,_$%'\"()<>= !\\[\\]{}@",
printable = "\x20-\x7e",
@@ -108,8 +119,19 @@ class Strings extends Operation {
}
const regex = new XRegExp(strings, "ig");
const results = search(
input,
regex,
null,
sort ? caseInsensitiveSort : 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

@@ -44,8 +44,8 @@ class Substitute extends Operation {
* @returns {string}
*/
run(input, args) {
const plaintext = Utils.expandAlphRange(args[0]).join(""),
ciphertext = Utils.expandAlphRange(args[1]).join("");
const plaintext = Utils.expandAlphRange([...args[0]]),
ciphertext = Utils.expandAlphRange([...args[1]]);
let output = "",
index = -1;
@@ -53,9 +53,9 @@ class Substitute extends Operation {
output = "Warning: Plaintext and Ciphertext lengths differ\n\n";
}
for (let i = 0; i < input.length; i++) {
index = plaintext.indexOf(input[i]);
output += index > -1 && index < ciphertext.length ? ciphertext[index] : input[i];
for (const character of input) {
index = plaintext.indexOf(character);
output += index > -1 && index < ciphertext.length ? ciphertext[index] : character;
}
return output;

View File

@@ -0,0 +1,82 @@
/**
* @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 Utils from "../Utils.mjs";
/**
* To Base45 operation
*/
class ToBase45 extends Operation {
/**
* ToBase45 constructor
*/
constructor() {
super();
this.name = "To 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 = "ArrayBuffer";
this.outputType = "string";
this.args = [
{
name: "Alphabet",
type: "string",
value: ALPHABET
}
];
this.highlight = highlightToBase45;
this.highlightReverse = highlightFromBase45;
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
if (!input) return "";
input = new Uint8Array(input);
const alphabet = Utils.expandAlphRange(args[0]);
const res = [];
for (const pair of Utils.chunked(input, 2)) {
let b = 0;
for (const e of pair) {
b *= 256;
b += e;
}
let chars = 0;
do {
res.push(alphabet[b % 45]);
chars++;
b = Math.floor(b / 45);
} while (b > 0);
if (chars < 2) {
res.push("0");
chars++;
}
if (pair.length > 1 && chars < 3) {
res.push("0");
}
}
return res.join("");
}
}
export default ToBase45;

View File

@@ -67,7 +67,7 @@ class ToHex extends Operation {
* @returns {Object[]} pos
*/
highlight(pos, args) {
let delim, commaLen;
let delim, commaLen = 0;
if (args[0] === "0x with comma") {
delim = "0x";
commaLen = 1;
@@ -86,7 +86,7 @@ class ToHex extends Operation {
pos[0].start = pos[0].start * (2 + len) + countLF(pos[0].start);
pos[0].end = pos[0].end * (2 + len) + countLF(pos[0].end);
// if the deliminators are not prepended, trim the trailing deliminator
// if the delimiters are not prepended, trim the trailing delimiter
if (!(delim === "0x" || delim === "\\x")) {
pos[0].end -= delim.length;
}

View File

@@ -5,6 +5,7 @@
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
/**
* To Upper case operation
@@ -37,25 +38,30 @@ class ToUpperCase extends Operation {
* @returns {string}
*/
run(input, args) {
if (!args || args.length === 0) {
throw new OperationError("No capitalization scope was provided.");
}
const scope = args[0];
switch (scope) {
case "Word":
return input.replace(/(\b\w)/gi, function(m) {
return m.toUpperCase();
});
case "Sentence":
return input.replace(/(?:\.|^)\s*(\b\w)/gi, function(m) {
return m.toUpperCase();
});
case "Paragraph":
return input.replace(/(?:\n|^)\s*(\b\w)/gi, function(m) {
return m.toUpperCase();
});
case "All": /* falls through */
default:
return input.toUpperCase();
if (scope === "All") {
return input.toUpperCase();
}
const scopeRegex = {
"Word": /(\b\w)/gi,
"Sentence": /(?:\.|^)\s*(\b\w)/gi,
"Paragraph": /(?:\n|^)\s*(\b\w)/gi
}[scope];
if (scopeRegex === undefined) {
throw new OperationError("Unrecognized capitalization scope");
}
// Use the regex to capitalize the input
return input.replace(scopeRegex, function(m) {
return m.toUpperCase();
});
}
/**

View File

@@ -22,7 +22,7 @@ class TripleDESDecrypt extends Operation {
this.name = "Triple DES Decrypt";
this.module = "Ciphers";
this.description = "Triple DES applies DES three times to each block to increase key size.<br><br><b>Key:</b> Triple DES uses a key length of 24 bytes (192 bits).<br>DES uses a key length of 8 bytes (64 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 = "Triple DES applies DES three times to each block to increase key size.<br><br><b>Key:</b> Triple DES uses a key length of 24 bytes (192 bits).<br>DES uses a key length of 8 bytes (64 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/Triple_DES";
this.inputType = "string";
this.outputType = "string";
@@ -42,7 +42,7 @@ class TripleDESDecrypt 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,8 @@ class TripleDESDecrypt 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 = args[2],
mode = args[2].substring(0, 3),
noPadding = args[2].endsWith("NoPadding"),
inputType = args[3],
outputType = args[4];
@@ -85,6 +86,14 @@ Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
input = Utils.convertToByteString(input, inputType);
const decipher = forge.cipher.createDecipher("3DES-" + mode, key);
/* 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

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

View File

@@ -26,9 +26,14 @@ class Unique extends Operation {
this.outputType = "string";
this.args = [
{
"name": "Delimiter",
"type": "option",
"value": INPUT_DELIM_OPTIONS
name: "Delimiter",
type: "option",
value: INPUT_DELIM_OPTIONS
},
{
name: "Display count",
type: "boolean",
value: false
}
];
}
@@ -39,8 +44,23 @@ class Unique extends Operation {
* @returns {string}
*/
run(input, args) {
const delim = Utils.charRep(args[0]);
return input.split(delim).unique().join(delim);
const delim = Utils.charRep(args[0]),
count = args[1];
if (count) {
const valMap = input.split(delim).reduce((acc, curr) => {
if (Object.prototype.hasOwnProperty.call(acc, curr)) {
acc[curr]++;
} else {
acc[curr] = 1;
}
return acc;
}, {});
return Object.keys(valMap).map(val => `${valMap[val]} ${val}`).join(delim);
} else {
return input.split(delim).unique().join(delim);
}
}
}

View File

@@ -10,7 +10,7 @@
import NodeDish from "./NodeDish.mjs";
import NodeRecipe from "./NodeRecipe.mjs";
import OperationConfig from "../core/config/OperationConfig.json";
import OperationConfig from "../core/config/OperationConfig.json" assert {type: "json"};
import { sanitise, removeSubheadingsFromArray, sentenceToCamelCase } from "./apiUtils.mjs";
import ExcludedOperationError from "../core/errors/ExcludedOperationError.mjs";

View File

@@ -1,13 +0,0 @@
/**
* Export the main ESM module as CommonJS
*
*
* @author d98762656 [d98762625@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
/* eslint no-global-assign: ["off"] */
require = require("esm")(module);
module.exports = require("./index.mjs");
module.exports.File = require("./File.mjs");

View File

@@ -41,7 +41,7 @@ let code = `/**
import NodeDish from "./NodeDish.mjs";
import { _wrap, help, bake, _explainExcludedFunction } from "./api.mjs";
import File from "./File.mjs";
import { OperationError, DishError, ExcludedOperationError } from "../core/errors/index";
import { OperationError, DishError, ExcludedOperationError } from "../core/errors/index.mjs";
import {
// import as core_ to avoid name clashes after wrap.
`;
@@ -52,7 +52,7 @@ includedOperations.forEach((op) => {
});
code +=`
} from "../core/operations/index";
} from "../core/operations/index.mjs";
global.File = File;

View File

@@ -7,8 +7,8 @@
* @license Apache-2.0
*/
const chef = require("./cjs.js");
const repl = require("repl");
import chef from "./index.mjs";
import repl from "repl";
/* eslint no-console: ["off"] */

11
src/node/wrapper.js Normal file
View File

@@ -0,0 +1,11 @@
/**
* Export the main ESM module as CommonJS
*
*
* @author d98762656 [d98762625@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
module.exports = (async () => await import("./index.mjs"))();
module.exports.File = (async () => await import("./File.mjs"))();

View File

@@ -57,7 +57,7 @@ class App {
this.populateOperationsList();
this.manager.setup();
this.manager.output.saveBombe();
this.resetLayout();
this.adjustComponentSizes();
this.setCompileMessage();
log.debug("App loaded");
@@ -295,9 +295,7 @@ class App {
gutterSize: 4,
expandToMin: true,
onDrag: debounce(function() {
this.manager.recipe.adjustWidth();
this.manager.input.calcMaxTabs();
this.manager.output.calcMaxTabs();
this.adjustComponentSizes();
}, 50, "dragSplitter", this, [])
});
@@ -307,7 +305,7 @@ class App {
minSize: minimise ? [0, 0] : [100, 100]
});
this.resetLayout();
this.adjustComponentSizes();
}
@@ -581,6 +579,13 @@ class App {
resetLayout() {
this.columnSplitter.setSizes([20, 30, 50]);
this.ioSplitter.setSizes([50, 50]);
this.adjustComponentSizes();
}
/**
* Adjust components to fit their containers.
*/
adjustComponentSizes() {
this.manager.recipe.adjustWidth();
this.manager.input.calcMaxTabs();
this.manager.output.calcMaxTabs();

View File

@@ -29,7 +29,7 @@
<meta name="description" content="The Cyber Swiss Army Knife - a web app for encryption, encoding, compression and data analysis" />
<meta name="keywords" content="base64, hex, decode, encode, encrypt, decrypt, compress, decompress, regex, regular expressions, hash, crypt, hexadecimal, user agent, url, certificate, x.509, parser, JSON, gzip, md5, sha1, aes, des, blowfish, xor" />
<link rel="icon" type="image/ico" href="<%- require('../static/images/favicon.ico').default %>" />
<link rel="icon" type="image/ico" href="<%- require('../static/images/favicon.ico') %>" />
<script type="application/javascript">
"use strict";
@@ -176,7 +176,7 @@
<div id="recipe" class="split split-horizontal no-select">
<div class="title no-select">
Recipe
<span class="float-right">
<span class="pane-controls hide-on-maximised-output">
<button type="button" class="btn btn-primary bmd-btn-icon" id="save" data-toggle="tooltip" title="Save recipe">
<i class="material-icons">save</i>
</button>
@@ -190,14 +190,14 @@
</div>
<ul id="rec-list" class="list-area no-select"></ul>
<div id="controls" class="no-select">
<div id="controls" class="no-select hide-on-maximised-output">
<div id="controls-content" class="d-flex align-items-center">
<button type="button" class="mx-2 btn btn-lg btn-secondary" id="step" data-toggle="tooltip" title="Step through the recipe">
Step
</button>
<button type="button" class="mx-2 btn btn-lg btn-success btn-raised btn-block" id="bake">
<img aria-hidden="true" src="<%- require('../static/images/cook_male-32x32.png').default %>" alt="Chef Icon"/>
<img aria-hidden="true" src="<%- require('../static/images/cook_male-32x32.png') %>" alt="Chef Icon"/>
<span>Bake!</span>
</button>
@@ -217,7 +217,10 @@
<div id="input" class="split no-select">
<div class="title no-select">
<label for="input-text">Input</label>
<span class="float-right">
<span class="pane-controls">
<div class="io-info" id="input-files-info"></div>
<div class="io-info" id="input-selection-info"></div>
<div class="io-info" id="input-info"></div>
<button type="button" class="btn btn-primary bmd-btn-icon" id="btn-new-tab" data-toggle="tooltip" title="Add a new input tab">
<i class="material-icons">add</i>
</button>
@@ -236,9 +239,7 @@
<i class="material-icons">view_compact</i>
</button>
</span>
<div class="io-info" id="input-files-info"></div>
<div class="io-info" id="input-info"></div>
<div class="io-info" id="input-selection-info"></div>
</div>
<div id="input-tabs-wrapper" style="display: none;" class="no-select">
<span id="btn-previous-input-tab" class="input-tab-buttons">
@@ -271,7 +272,7 @@
<div class="file-overlay" id="file-overlay"></div>
<div style="position: relative; height: 100%;">
<div class="io-card card">
<img aria-hidden="true" src="<%- require('../static/images/file-128x128.png').default %>" alt="File icon" id="input-file-thumbnail"/>
<img aria-hidden="true" src="<%- require('../static/images/file-128x128.png') %>" alt="File icon" id="input-file-thumbnail"/>
<div class="card-body">
<button type="button" class="close" id="input-file-close">&times;</button>
Name: <span id="input-file-name"></span><br>
@@ -288,7 +289,10 @@
<div id="output" class="split">
<div class="title no-select">
<label for="output-text">Output</label>
<span class="float-right">
<span class="pane-controls">
<div class="io-info" id="bake-info"></div>
<div class="io-info" id="output-selection-info"></div>
<div class="io-info" id="output-info"></div>
<button type="button" class="btn btn-primary bmd-btn-icon" id="save-all-to-file" data-toggle="tooltip" title="Save all outputs to a zip file" style="display: none">
<i class="material-icons">archive</i>
</button>
@@ -308,9 +312,7 @@
<i class="material-icons">fullscreen</i>
</button>
</span>
<div class="io-info" id="bake-info"></div>
<div class="io-info" id="output-info"></div>
<div class="io-info" id="output-selection-info"></div>
<button type="button" class="btn btn-primary bmd-btn-icon hidden" id="magic" data-toggle="tooltip" title="Magic!" data-html="true">
<svg width="22" height="22" viewBox="0 0 24 24">
<path d="M7.5,5.6L5,7L6.4,4.5L5,2L7.5,3.4L10,2L8.6,4.5L10,7L7.5,5.6M19.5,15.4L22,14L20.6,16.5L22,19L19.5,17.6L17,19L18.4,16.5L17,14L19.5,15.4M22,2L20.6,4.5L22,7L19.5,5.6L17,7L18.4,4.5L17,2L19.5,3.4L22,2M13.34,12.78L15.78,10.34L13.66,8.22L11.22,10.66L13.34,12.78M14.37,7.29L16.71,9.63C17.1,10 17.1,10.65 16.71,11.04L5.04,22.71C4.65,23.1 4,23.1 3.63,22.71L1.29,20.37C0.9,20 0.9,19.35 1.29,18.96L12.96,7.29C13.35,6.9 14,6.9 14.37,7.29Z" />
@@ -346,12 +348,12 @@
<div id="output-highlighter" class="no-select"></div>
<div id="output-html"></div>
<textarea id="output-text" readonly="readonly" spellcheck="false"></textarea>
<img id="show-file-overlay" aria-hidden="true" src="<%- require('../static/images/file-32x32.png').default %>" alt="Show file overlay" title="Show file overlay"/>
<img id="show-file-overlay" aria-hidden="true" src="<%- require('../static/images/file-32x32.png') %>" alt="Show file overlay" title="Show file overlay"/>
<div id="output-file">
<div class="file-overlay"></div>
<div style="position: relative; height: 100%;">
<div class="io-card card">
<img aria-hidden="true" src="<%- require('../static/images/file-128x128.png').default %>" alt="File icon"/>
<img aria-hidden="true" src="<%- require('../static/images/file-128x128.png') %>" alt="File icon"/>
<div class="card-body">
Size: <span id="output-file-size"></span><br>
<button id="output-file-download" type="button" class="btn btn-primary btn-outline">Download</button>
@@ -626,7 +628,7 @@
<h5 class="modal-title">CyberChef - The Cyber Swiss Army Knife</h5>
</div>
<div class="modal-body">
<img aria-hidden="true" class="about-img-left" src="<%- require('../static/images/cyberchef-128x128.png').default %>" alt="CyberChef Logo"/>
<img aria-hidden="true" class="about-img-left" src="<%- require('../static/images/cyberchef-128x128.png') %>" alt="CyberChef Logo"/>
<p class="subtext">
Version <%= htmlWebpackPlugin.options.version %><br>
Compile time: <%= htmlWebpackPlugin.options.compileTime %>
@@ -749,7 +751,7 @@
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
<a href="https://github.com/gchq/CyberChef">
<img aria-hidden="true" style="position: absolute; top: 0; right: 0; border: 0;" src="<%- require('../static/images/fork_me.png').default %>" alt="Fork me on GitHub">
<img aria-hidden="true" style="position: absolute; top: 0; right: 0; border: 0;" src="<%- require('../static/images/fork_me.png') %>" alt="Fork me on GitHub">
</a>
</div>
</div>

View File

@@ -17,8 +17,8 @@ import * as CanvasComponents from "../core/lib/CanvasComponents.mjs";
// CyberChef
import App from "./App.mjs";
import Categories from "../core/config/Categories.json";
import OperationConfig from "../core/config/OperationConfig.json";
import Categories from "../core/config/Categories.json" assert {type: "json"};
import OperationConfig from "../core/config/OperationConfig.json" assert {type: "json"};
/**

View File

@@ -1,5 +1,5 @@
import sm from "sitemap";
import OperationConfig from "../../core/config/OperationConfig.json";
import OperationConfig from "../../core/config/OperationConfig.json" assert {type: "json"};
/**

View File

@@ -24,9 +24,16 @@
line-height: calc(var(--title-height) - 14px);
}
.title>span,
.title>.btn {
margin-top: -4px;
.pane-controls {
position: absolute;
right: 8px;
top: 8px;
display: flex;
flex-direction: row;
}
.pane-controls .btn {
margin-left: 2px;
}
.list-area {
@@ -107,4 +114,4 @@
#files .card-header .float-right a:hover {
text-decoration: none;
}
}

View File

@@ -34,3 +34,6 @@
@import "./layout/_operations.css";
@import "./layout/_recipe.css";
@import "./layout/_structure.css";
/* Operations */
@import "./operations/json.css";

View File

@@ -58,6 +58,10 @@
border-radius: 30px;
}
.output-maximised .hide-on-maximised-output {
display: none !important;
}
.spin {
animation-name: spin;
animation-duration: 3s;

View File

@@ -280,9 +280,8 @@
}
.io-info {
margin-right: 20px;
margin-right: 18px;
margin-top: 1px;
float: right;
height: 30px;
text-align: right;
line-height: 12px;
@@ -303,7 +302,7 @@
#stale-indicator {
opacity: 1;
visibility: visibile;
visibility: visible;
transition: margin 0s, opacity 0.3s;
margin-left: 5px;
cursor: help;
@@ -316,7 +315,7 @@
#magic {
opacity: 1;
visibility: visibile;
visibility: visible;
transition: margin 0s 0.3s, opacity 0.3s 0.3s, visibility 0.3s 0.3s;
margin-left: 5px;
margin-bottom: 5px;

View File

@@ -39,8 +39,8 @@ div#output {
.split {
box-sizing: border-box;
/* overflow: auto;
Removed to enable Background Magic button pulse to overflow.
/* overflow: auto; */
/* Removed to enable Background Magic button pulse to overflow.
Replace this rule if it seems to be causing problems. */
position: relative;
}

View File

@@ -0,0 +1,78 @@
/**
* JSON styles
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2022
* @license Apache-2.0
*
* Adapted for CyberChef by @n1474335 from jQuery json-viewer
* @author Alexandre Bodelot <alexandre.bodelot@gmail.com>
* @link https://github.com/abodelot/jquery.json-viewer
* @license MIT
*/
/* Root element */
.json-document {
padding: .5em 1.5em;
}
/* Syntax highlighting for JSON objects */
ul.json-dict, ol.json-array {
list-style-type: none;
margin: 0 0 0 1px;
border-left: 1px dotted #ccc;
padding-left: 2em;
}
.json-string {
color: green;
}
.json-literal {
color: red;
}
.json-brace,
.json-bracket,
.json-colon,
.json-comma {
color: gray;
}
/* Collapse */
.json-details {
display: inline;
}
.json-details[open] {
display: contents;
}
.json-summary {
display: contents;
}
/* Display object and array brackets when closed */
.json-summary.json-obj::after {
color: gray;
content: "{ ... }"
}
.json-summary.json-arr::after {
color: gray;
content: "[ ... ]"
}
.json-details[open] > .json-summary.json-obj::after,
.json-details[open] > .json-summary.json-arr::after {
content: "";
}
/* Show arrows, even in inline mode */
.json-summary::before {
content: "\25BC";
color: #c0c0c0;
margin-left: -12px;
margin-right: 5px;
display: inline-block;
transform: rotate(-90deg);
}
.json-summary:hover::before {
color: #aaa;
}
.json-details[open] > .json-summary::before {
transform: rotate(0deg);
}

View File

@@ -109,11 +109,15 @@ class OperationsWaiter {
const matchedOps = [];
const matchedDescs = [];
// Create version with no whitespace for the fuzzy match
// Helps avoid missing matches e.g. query "TCP " would not find "Parse TCP"
const inStrNWS = inStr.replace(/\s/g, "");
for (const opName in this.app.operations) {
const op = this.app.operations[opName];
// Match op name using fuzzy match
const [nameMatch, score, idxs] = fuzzyMatch(inStr, opName);
const [nameMatch, score, idxs] = fuzzyMatch(inStrNWS, opName);
// Match description based on exact match
const descPos = op.description.toLowerCase().indexOf(inStr.toLowerCase());

View File

@@ -1373,6 +1373,7 @@ class OutputWaiter {
const el = e.target.id === "maximise-output" ? e.target : e.target.parentNode;
if (el.getAttribute("data-original-title").indexOf("Maximise") === 0) {
document.body.classList.add("output-maximised");
this.app.initialiseSplitter(true);
this.app.columnSplitter.collapse(0);
this.app.columnSplitter.collapse(1);
@@ -1381,6 +1382,7 @@ class OutputWaiter {
$(el).attr("data-original-title", "Restore output pane");
el.querySelector("i").innerHTML = "fullscreen_exit";
} else {
document.body.classList.remove("output-maximised");
$(el).attr("data-original-title", "Maximise output pane");
el.querySelector("i").innerHTML = "fullscreen";
this.app.initialiseSplitter(false);

View File

@@ -23,11 +23,11 @@ class WindowWaiter {
/**
* Handler for window resize events.
* Resets the layout of CyberChef's panes after 200ms (so that continuous resizing doesn't cause
* Resets adjustable component sizes after 200ms (so that continuous resizing doesn't cause
* continuous resetting).
*/
windowResize() {
debounce(this.app.resetLayout, 200, "windowResize", this.app, [])();
debounce(this.app.adjustComponentSizes, 200, "windowResize", this.app, [])();
}

View File

@@ -71,7 +71,7 @@ module.exports = {
.moveToElement(toHex, 10, 10)
.useCss()
.waitForElementVisible(".popover-body", 1000)
.doubleClick();
.doubleClick("xpath", toHex);
// Confirm that it has been added to the recipe
browser
@@ -90,7 +90,7 @@ module.exports = {
browser
.useCss()
.waitForElementNotVisible("#stale-indicator", 1000)
.expect.element("#output-text").to.have.value.that.equals("44 6f 6e 27 74 20 50 61 6e 69 63 2e");
.expect.element("#output-text").to.have.property("value").that.equals("44 6f 6e 27 74 20 50 61 6e 69 63 2e");
// Clear recipe
browser
@@ -202,11 +202,11 @@ module.exports = {
browser
.getLocationInView(genUUID)
.moveToElement(genUUID, 10, 10)
.doubleClick()
.doubleClick("xpath", genUUID)
.useCss()
.waitForElementVisible(".operation .op-title", 1000)
.waitForElementNotVisible("#stale-indicator", 1000)
.expect.element("#output-text").to.have.value.which.matches(/[\da-f-]{36}/);
.expect.element("#output-text").to.have.property("value").which.matches(/[\da-f-]{36}/);
browser.click("#clr-recipe");
},

View File

@@ -16,7 +16,8 @@ module.exports = {
.click("#auto-bake-label");
},
"Sanity check operations": browser => {
"Sanity check operations": async browser => {
const Images = await import("../samples/Images.mjs");
testOp(browser, "A1Z26 Cipher Decode", "20 5 19 20 15 21 20 16 21 20", "testoutput");
testOp(browser, "A1Z26 Cipher Encode", "test input", "20 5 19 20 9 14 16 21 20");
testOp(browser, "ADD", "test input", "Ê»ÉÊv¿ÄÆËÊ", [{ "option": "Hex", "string": "56" }]);
@@ -24,7 +25,7 @@ module.exports = {
testOp(browser, "AES Encrypt", "test input", "e42eb8fbfb7a98fff061cd2c1a794d92", [{"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, {"option": "Hex", "string": "00000000000000000000000000000000"}, "CBC", "Raw", "Hex"]);
testOp(browser, "AND", "test input", "4$04 $044", [{ "option": "Hex", "string": "34" }]);
testOp(browser, "Add line numbers", "test input", "1 test input");
// testOp(browser, "Add Text To Image", "test input", "test_output");
testOp(browser, ["From Hex", "Add Text To Image", "To Base64"], Images.PNG_HEX, Images.PNG_CHEF_B64, [[], ["Chef", "Center", "Middle", 0, 0, 16], []]);
testOp(browser, "Adler-32 Checksum", "test input", "16160411");
testOp(browser, "Affine Cipher Decode", "test input", "rcqr glnsr", [1, 2]);
testOp(browser, "Affine Cipher Encode", "test input", "njln rbfpn", [2, 1]);
@@ -44,12 +45,12 @@ module.exports = {
// testOp(browser, "Bifid Cipher Encode", "test input", "test_output");
// testOp(browser, "Bit shift left", "test input", "test_output");
// testOp(browser, "Bit shift right", "test input", "test_output");
// testOp(browser, "Blowfish Decrypt", "test input", "test_output");
// testOp(browser, "Blowfish Encrypt", "test input", "test_output");
// testOp(browser, "Blur Image", "test input", "test_output");
testOp(browser, "Blowfish Decrypt", "10884e15427dd84ec35204e9c8e921ae", "test_output", [{"option": "Hex", "string": "1234567801234567"}, {"option": "Hex", "string": "0011223344556677"}, "CBC", "Hex", "Raw"]);
testOp(browser, "Blowfish Encrypt", "test input", "f0fadbd1d90d774f714248cf26b96410", [{"option": "Hex", "string": "1234567801234567"}, {"option": "Hex", "string": "0011223344556677"}, "CBC", "Raw", "Hex"]);
testOp(browser, ["From Hex", "Blur Image", "To Base64"], Images.PNG_HEX, Images.PNG_BLUR_B64);
// testOp(browser, "Bombe", "test input", "test_output");
// testOp(browser, "Bzip2 Compress", "test input", "test_output");
// testOp(browser, "Bzip2 Decompress", "test input", "test_output");
testOp(browser, ["Bzip2 Compress", "To Hex"], "test input", "42 5a 68 39 31 41 59 26 53 59 cf 96 82 1d 00 00 03 91 80 40 00 02 21 4e 00 20 00 21 90 c2 10 c0 88 33 92 8e df 17 72 45 38 50 90 cf 96 82 1d");
testOp(browser, ["From Hex", "Bzip2 Decompress"], "425a68393141592653597b0884b7000003038000008200ce00200021a647a4218013709517c5dc914e14241ec2212dc0", "test_output", [[], [true]]);
// testOp(browser, "CRC-16 Checksum", "test input", "test_output");
// testOp(browser, "CRC-32 Checksum", "test input", "test_output");
// testOp(browser, "CRC-8 Checksum", "test input", "test_output");
@@ -69,10 +70,10 @@ module.exports = {
// testOp(browser, "Comment", "test input", "test_output");
// testOp(browser, "Compare CTPH hashes", "test input", "test_output");
// testOp(browser, "Compare SSDEEP hashes", "test input", "test_output");
// testOp(browser, "Conditional Jump", "test input", "test_output");
// /testOp(browser, "Conditional Jump", "test input", "test_output");
// testOp(browser, "Contain Image", "test input", "test_output");
// testOp(browser, "Convert area", "test input", "test_output");
// testOp(browser, "Convert co-ordinate format", "test input", "test_output");
// /testOp(browser, "Convert co-ordinate format", "test input", "test_output");
// testOp(browser, "Convert data units", "test input", "test_output");
// testOp(browser, "Convert distance", "test input", "test_output");
// testOp(browser, "Convert Image Format", "test input", "test_output");
@@ -197,10 +198,12 @@ module.exports = {
// testOp(browser, "MD4", "test input", "test_output");
// testOp(browser, "MD5", "test input", "test_output");
// testOp(browser, "MD6", "test input", "test_output");
// testOp(browser, "Magic", "test input", "test_output");
testOpHtml(browser, "Magic", "dGVzdF9vdXRwdXQ=", "tr:nth-of-type(1) th:nth-of-type(2)", "Result snippet");
testOpHtml(browser, "Magic", "dGVzdF9vdXRwdXQ=", "tr:nth-of-type(2) td:nth-of-type(2)", "test_output");
testOpHtml(browser, "Magic", "dGVzdF9vdXRwdXQ=", "tr:nth-of-type(2) td:nth-of-type(1)", /Base64/);
// testOp(browser, "Mean", "test input", "test_output");
// testOp(browser, "Median", "test input", "test_output");
// testOp(browser, "Merge", "test input", "test_output");
// testOp(browser, "Median", "test input", "test_output");`
// testOp(browser, "Merge", "test input", "test_output");`
// testOp(browser, "Microsoft Script Decoder", "test input", "test_output");
// testOp(browser, "Multiple Bombe", "test input", "test_output");
// testOp(browser, "Multiply", "test input", "test_output");
@@ -372,34 +375,50 @@ module.exports = {
}
};
/**
* Clears the current recipe and tests a new operation.
/** @function
* Clears the current recipe and bakes a new operation.
*
* @param {string} opName
* @param {Browser} browser
* @param {Browser} browser - Nightwatch client
* @param {string|Array<string>} opName - name of operation to be tested, array for multiple ops
* @param {string} input - input text for test
* @param {Array<string>|Array<Array<string>>} args - aarguments, nested if multiple ops
*/
function testOp(browser, opName, input, output, args=[]) {
function bakeOp(browser, opName, input, args=[]) {
let recipeConfig;
const recipeConfig = JSON.stringify([{
"op": opName,
"args": args
}]);
if (typeof(opName) === "string") {
recipeConfig = JSON.stringify([{
"op": opName,
"args": args
}]);
} else if (opName instanceof Array) {
recipeConfig = JSON.stringify(
opName.map((op, i) => {
return {
op: op,
args: args.length ? args[i] : []
};
})
);
} else {
throw new Error("Invalid operation type. Must be string or array of strings. Received: " + typeof(opName));
}
browser
.perform(function() {
console.log(opName);
})
.useCss()
.click("#clr-recipe")
.click("#clr-io")
.waitForElementNotPresent("#rec-list li.operation")
.expect.element("#input-text").to.have.value.that.equals("");
.expect.element("#input-text").to.have.property("value").that.equals("");
browser
.perform(function() {
console.log(`Current test: ${opName}`);
})
.urlHash("recipe=" + recipeConfig)
.setValue("#input-text", input)
.waitForElementPresent("#rec-list li.operation")
.expect.element("#input-text").to.have.value.that.equals(input);
.expect.element("#input-text").to.have.property("value").that.equals(input);
browser
.waitForElementVisible("#stale-indicator", 5000)
@@ -408,10 +427,44 @@ function testOp(browser, opName, input, output, args=[]) {
.pause(100)
.waitForElementPresent("#stale-indicator.hidden", 5000)
.waitForElementNotVisible("#output-loader", 5000);
}
/** @function
* Clears the current recipe and tests a new operation.
*
* @param {Browser} browser - Nightwatch client
* @param {string|Array<string>} opName - name of operation to be tested, array for multiple ops
* @param {string} input - input text
* @param {string} output - expected output
* @param {Array<string>|Array<Array<string>>} args - arguments, nested if multiple ops
*/
function testOp(browser, opName, input, output, args=[]) {
bakeOp(browser, opName, input, args);
if (typeof output === "string") {
browser.expect.element("#output-text").to.have.value.that.equals(output);
browser.expect.element("#output-text").to.have.property("value").that.equals(output);
} else if (output instanceof RegExp) {
browser.expect.element("#output-text").to.have.value.that.matches(output);
browser.expect.element("#output-text").to.have.property("value").that.matches(output);
}
}
/** @function
* Clears the current recipe and tests a new operation.
*
* @param {Browser} browser - Nightwatch client
* @param {string|Array<string>} opName - name of operation to be tested array for multiple ops
* @param {string} input - input text
* @param {string} cssSelector - CSS selector for HTML output
* @param {string} output - expected output
* @param {Array<string>|Array<Array<string>>} args - arguments, nested if multiple ops
*/
function testOpHtml(browser, opName, input, cssSelector, output, args=[]) {
bakeOp(browser, opName, input, args);
if (typeof output === "string") {
browser.expect.element("#output-html " + cssSelector).text.that.equals(output);
} else if (output instanceof RegExp) {
browser.expect.element("#output-html " + cssSelector).text.that.matches(output);
}
}

View File

@@ -6,24 +6,27 @@
* @license Apache-2.0
*/
const chef = require("cyberchef");
const assert = require("assert");
const d = chef.bake("Testing, 1 2 3", [
chef.toHex,
chef.reverse,
{
op: chef.unique,
args: {
delimiter: "Space",
}
},
{
op: chef.multiply,
args: {
delimiter: "Space",
}
}
]);
require("cyberchef").then(chef => {
assert.equal(d.value, "630957449041920");
const d = chef.bake("Testing, 1 2 3", [
chef.toHex,
chef.reverse,
{
op: chef.unique,
args: {
delimiter: "Space",
}
},
{
op: chef.multiply,
args: {
delimiter: "Space",
}
}
]);
assert.equal(d.value, "630957449041920");
});

View File

@@ -7,8 +7,28 @@
*/
import assert from "assert";
import chef from "cyberchef";
import { bake, toHex, reverse, unique, multiply } from "cyberchef";
const d = chef.bake("Testing, 1 2 3", [
const a = bake("Testing, 1 2 3", [
toHex,
reverse,
{
op: unique,
args: {
delimiter: "Space",
}
},
{
op: multiply,
args: {
delimiter: "Space",
}
}
]);
assert.equal(a.value, "630957449041920");
const b = chef.bake("Testing, 1 2 3", [
chef.toHex,
chef.reverse,
{
@@ -25,4 +45,4 @@ const d = chef.bake("Testing, 1 2 3", [
}
]);
assert.equal(d.value, "630957449041920");
assert.equal(b.value, "630957449041920");

View File

@@ -1,28 +0,0 @@
/**
* Tests to ensure that a consuming app can use named imports from deep import patch
*
* @author d98762625 [d98762625@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import assert from "assert";
import { bake, toHex, reverse, unique, multiply } from "cyberchef/src/node/index.mjs";
const d = bake("Testing, 1 2 3", [
toHex,
reverse,
{
op: unique,
args: {
delimiter: "Space",
}
},
{
op: multiply,
args: {
delimiter: "Space",
}
}
]);
assert.equal(d.value, "630957449041920");

View File

@@ -1,6 +1,6 @@
import TestRegister from "../../lib/TestRegister.mjs";
import Categories from "../../../src/core/config/Categories.json";
import OperationConfig from "../../../src/core/config/OperationConfig.json";
import Categories from "../../../src/core/config/Categories.json" assert {type: "json"};
import OperationConfig from "../../../src/core/config/OperationConfig.json" assert {type: "json"};
import it from "../assertionHandler.mjs";
import assert from "assert";

View File

@@ -119,7 +119,7 @@ TestRegister.addApiTests([
assert.strictEqual(result[0].module, "Ciphers");
assert.strictEqual(result[0].inputType, "string");
assert.strictEqual(result[0].outputType, "string");
assert.strictEqual(result[0].description, "Triple DES applies DES three times to each block to increase key size.<br><br><b>Key:</b> Triple DES uses a key length of 24 bytes (192 bits).<br>DES uses a key length of 8 bytes (64 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.");
assert.strictEqual(result[0].description, "Triple DES applies DES three times to each block to increase key size.<br><br><b>Key:</b> Triple DES uses a key length of 24 bytes (192 bits).<br>DES uses a key length of 8 bytes (64 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.");
assert.strictEqual(result[0].args.length, 5);
}),

View File

@@ -32,7 +32,7 @@ import {
CSSMinify,
toBase64,
toHex
} from "../../../src/node/index";
} from "../../../src/node/index.mjs";
import chef from "../../../src/node/index.mjs";
import TestRegister from "../../lib/TestRegister.mjs";
import File from "../../../src/node/File.mjs";
@@ -471,7 +471,7 @@ color: white;
}),
it("Extract dates", () => {
assert.strictEqual(chef.extractDates("Don't Look a Gift Horse In The Mouth 01/02/1992").toString(), "01/02/1992\n");
assert.strictEqual(chef.extractDates("Don't Look a Gift Horse In The Mouth 01/02/1992").toString(), "01/02/1992");
}),
it("Filter", () => {
@@ -685,8 +685,8 @@ Arguments:
it("Parse user agent", () => {
const result = chef.parseUserAgent("Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0 ");
const expected = `Browser
Name: Mozilla
Version: 5.0
Name: Firefox
Version: 47.0
Device
Model: unknown
Type: unknown
@@ -816,7 +816,7 @@ pCGTErs=
it("RC4 Drop", () => {
assert.strictEqual(
chef.RC4Drop("Go Out On a Limb", {passphrase: {string: "Under Your Nose", option: "UTF8"}, inputFormat: "UTF8", outputFormat: "Hex"}).toString(),
"8fa5f2751d34476a0c857439f43816cf");
"b85cb1c4ed6bed8f260ab92829bba942");
}),
it("Regular Expression", () => {
@@ -859,7 +859,7 @@ pCGTErs=
}),
it("SQL Beautify", () => {
const result = chef.SQLBeautify(`SELECT MONTH, ID, RAIN_I, TEMP_F
const result = chef.SQLBeautify(`SELECT MONTH, ID, RAIN_I, TEMP_F
FROM STATS;`);
const expected = `SELECT MONTH,
ID,
@@ -879,8 +879,7 @@ FROM STATS;`;
const result = chef.strings("smothering ampersand abreast", {displayTotal: true});
const expected = `Total found: 1
smothering ampersand abreast
`;
smothering ampersand abreast`;
assert.strictEqual(result.toString(), expected);
}),

View File

@@ -20,6 +20,7 @@ import TestRegister from "../lib/TestRegister.mjs";
import "./tests/BCD.mjs";
import "./tests/BSON.mjs";
import "./tests/BaconCipher.mjs";
import "./tests/Base45.mjs";
import "./tests/Base58.mjs";
import "./tests/Base64.mjs";
import "./tests/Base62.mjs";
@@ -66,6 +67,7 @@ import "./tests/PGP.mjs";
import "./tests/PHP.mjs";
import "./tests/ParseIPRange.mjs";
import "./tests/ParseQRCode.mjs";
import "./tests/PEMtoHex.mjs";
import "./tests/PowerSet.mjs";
import "./tests/Regex.mjs";
import "./tests/Register.mjs";
@@ -74,6 +76,7 @@ import "./tests/SeqUtils.mjs";
import "./tests/SetDifference.mjs";
import "./tests/SetIntersection.mjs";
import "./tests/SetUnion.mjs";
import "./tests/SM4.mjs";
import "./tests/StrUtils.mjs";
import "./tests/SymmetricDifference.mjs";
import "./tests/TextEncodingBruteForce.mjs";
@@ -94,6 +97,7 @@ import "./tests/Protobuf.mjs";
import "./tests/ParseSSHHostKey.mjs";
import "./tests/DefangIP.mjs";
import "./tests/ParseUDP.mjs";
import "./tests/ParseTCP.mjs";
import "./tests/AvroToJSON.mjs";
import "./tests/Lorenz.mjs";
import "./tests/LuhnChecksum.mjs";
@@ -107,6 +111,8 @@ import "./tests/CBORDecode.mjs";
import "./tests/JA3Fingerprint.mjs";
import "./tests/JA3SFingerprint.mjs";
import "./tests/HASSH.mjs";
import "./tests/GetAllCasings.mjs";
import "./tests/SIGABA.mjs";
// Cannot test operations that use the File type yet

View File

@@ -6,7 +6,7 @@
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister";
import TestRegister from "../../lib/TestRegister.mjs";
TestRegister.addTests([
{

View File

@@ -5,8 +5,8 @@
* @copyright Karsten Silkenbäumer 2019
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister";
import { BACON_ALPHABETS, BACON_TRANSLATIONS } from "../../../src/core/lib/Bacon";
import TestRegister from "../../lib/TestRegister.mjs";
import { BACON_ALPHABETS, BACON_TRANSLATIONS } from "../../../src/core/lib/Bacon.mjs";
const alphabets = Object.keys(BACON_ALPHABETS);
const translations = BACON_TRANSLATIONS;

View File

@@ -0,0 +1,103 @@
/**
* Base45 tests.
*
* @author Thomas Weißschuh [thomas@t-8ch.de]
*
* @copyright Crown Copyright 2021
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
const defaultB45Alph = "0-9A-Z $%*+\\-./:";
TestRegister.addTests([
{
name: "To Base45: nothing",
input: "",
expectedOutput: "",
recipeConfig: [
{
op: "To Base45",
args: [defaultB45Alph],
},
],
},
{
name: "To Base45: Spec encoding example 1",
input: "AB",
expectedOutput: "BB8",
recipeConfig: [
{
op: "To Base45",
args: [defaultB45Alph],
},
],
},
{
name: "To Base45: Spec encoding example 2",
input: "Hello!!",
expectedOutput: "%69 VD92EX0",
recipeConfig: [
{
op: "To Base45",
args: [defaultB45Alph],
},
],
},
{
name: "To Base45: Spec encoding example 3",
input: "base-45",
expectedOutput: "UJCLQE7W581",
recipeConfig: [
{
op: "To Base45",
args: [defaultB45Alph],
},
],
},
{
name: "From Base45: nothing",
input: "",
expectedOutput: "",
recipeConfig: [
{
op: "From Base45",
args: [defaultB45Alph],
},
],
},
{
name: "From Base45: Spec decoding example 1",
input: "QED8WEX0",
expectedOutput: "ietf!",
recipeConfig: [
{
op: "From Base45",
args: [defaultB45Alph],
},
],
},
{
name: "From Base45: Invalid character",
input: "!",
expectedOutput: "Character not in alphabet: '!'",
recipeConfig: [
{
op: "From Base45",
args: [defaultB45Alph],
},
],
},
{
name: "From Base45: Invalid triplet value",
input: "ZZZ",
expectedOutput: "Triplet too large: 'ZZZ'",
recipeConfig: [
{
op: "From Base45",
args: [defaultB45Alph],
},
],
},
]);

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