mirror of
https://github.com/gchq/CyberChef
synced 2025-12-05 23:53:27 +00:00
Compare commits
361 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
904c08c436 | ||
|
|
fa238545a0 | ||
|
|
3f85c32c7c | ||
|
|
55809b2e87 | ||
|
|
5c32c1bdaa | ||
|
|
657f8940fa | ||
|
|
74d629c491 | ||
|
|
130e9d8192 | ||
|
|
0fb3743b6d | ||
|
|
a19aea1516 | ||
|
|
137f8d9471 | ||
|
|
1f57f1f000 | ||
|
|
468071ee98 | ||
|
|
23403f55a5 | ||
|
|
c25cf44d92 | ||
|
|
e5644b5712 | ||
|
|
9321b79d81 | ||
|
|
e97d535ff8 | ||
|
|
7589361e58 | ||
|
|
f15ef81693 | ||
|
|
d5f4968664 | ||
|
|
74e9edbccf | ||
|
|
51229d85cb | ||
|
|
b979b051cb | ||
|
|
17cf154bc2 | ||
|
|
c7f6954b97 | ||
|
|
9ccc1613cf | ||
|
|
9730ce1f6a | ||
|
|
55a7981547 | ||
|
|
2d99c365dd | ||
|
|
59d8be511a | ||
|
|
8349ffc001 | ||
|
|
c1368c4ecb | ||
|
|
c6935e040d | ||
|
|
f79c3ae91a | ||
|
|
bc27cd2772 | ||
|
|
2b02c44ca4 | ||
|
|
59fe8d1c4b | ||
|
|
9a5d62c4c3 | ||
|
|
9fa82150ee | ||
|
|
d7561ec208 | ||
|
|
743b834f6d | ||
|
|
0658836f87 | ||
|
|
a4e20c7059 | ||
|
|
c04f409d23 | ||
|
|
1a9833132d | ||
|
|
9c3ddca269 | ||
|
|
72889d1c20 | ||
|
|
6c5433b226 | ||
|
|
31a7f83b82 | ||
|
|
39143fa6a1 | ||
|
|
ebe2a29543 | ||
|
|
1e83e0e935 | ||
|
|
fe9eb08648 | ||
|
|
2255c5b360 | ||
|
|
c046cf5695 | ||
|
|
3086c25079 | ||
|
|
3700780d14 | ||
|
|
5b134d7e9e | ||
|
|
58b1fb8de5 | ||
|
|
c0bd6645ce | ||
|
|
cb023089bb | ||
|
|
5a507aa1ba | ||
|
|
d5ffbbb14c | ||
|
|
fa30f597ad | ||
|
|
ed8bd34915 | ||
|
|
5c72791279 | ||
|
|
142f91425c | ||
|
|
d6344760ec | ||
|
|
64c009f266 | ||
|
|
a73decc792 | ||
|
|
f332ca4617 | ||
|
|
937791d33d | ||
|
|
a63a130723 | ||
|
|
0f1175bf15 | ||
|
|
e4db23f857 | ||
|
|
32e7dd030e | ||
|
|
e33950961e | ||
|
|
5d65cb419f | ||
|
|
536053d5f9 | ||
|
|
04ef095b88 | ||
|
|
66277cd71f | ||
|
|
58f01d0464 | ||
|
|
9ba9c56361 | ||
|
|
11902e3220 | ||
|
|
c3f79c4b2c | ||
|
|
576905e8b8 | ||
|
|
77a3b91afe | ||
|
|
40b58aa144 | ||
|
|
d5bcdc8eed | ||
|
|
8e57354307 | ||
|
|
126debf44e | ||
|
|
674649ca7f | ||
|
|
1a9a070c3b | ||
|
|
32bee35f85 | ||
|
|
a68ce5a5af | ||
|
|
026e9ca9c3 | ||
|
|
f1ce67d79b | ||
|
|
312be4772c | ||
|
|
be97a0062e | ||
|
|
00f0101723 | ||
|
|
f450240094 | ||
|
|
a7b8378736 | ||
|
|
98a70c2dd2 | ||
|
|
d502dd9857 | ||
|
|
1ec7033d46 | ||
|
|
28ec56a27f | ||
|
|
bf2afcd2ef | ||
|
|
8f710461da | ||
|
|
c2cf535f88 | ||
|
|
ced9ab68fa | ||
|
|
cdb197a9c3 | ||
|
|
c8eacb9942 | ||
|
|
1c8e37cb64 | ||
|
|
1b0ced9f9b | ||
|
|
7b245b084a | ||
|
|
b00f64518f | ||
|
|
c3434e894d | ||
|
|
dd66f728b3 | ||
|
|
e40142b8c5 | ||
|
|
1dd1b839b8 | ||
|
|
d90d845f27 | ||
|
|
8c9ad81039 | ||
|
|
cef7a7b27d | ||
|
|
3e715ef21a | ||
|
|
86b43b4ffa | ||
|
|
65d883496b | ||
|
|
69e59916e2 | ||
|
|
475282984b | ||
|
|
2f89130f41 | ||
|
|
e9dd7eceb8 | ||
|
|
037590f831 | ||
|
|
85496684d8 | ||
|
|
4200ed4eb9 | ||
|
|
6b16f11d3b | ||
|
|
683bd3e5db | ||
|
|
6a10e94bfd | ||
|
|
25086386c6 | ||
|
|
d99ee32cc4 | ||
|
|
f1d318f229 | ||
|
|
a7fc455e05 | ||
|
|
c02c4a72e4 | ||
|
|
f97ce18ff9 | ||
|
|
dfd9afc2c4 | ||
|
|
eb5663a1ed | ||
|
|
418a7962a5 | ||
|
|
2ffce23c67 | ||
|
|
b828b50ccc | ||
|
|
a6aa40db97 | ||
|
|
45ede4beaf | ||
|
|
98a95c8bbf | ||
|
|
74bb8d92dc | ||
|
|
6cccc2c786 | ||
|
|
99a0a05947 | ||
|
|
94700dab89 | ||
|
|
c9d29c89bb | ||
|
|
7d4e554571 | ||
|
|
2858a74cbf | ||
|
|
28e599a835 | ||
|
|
1fb1d9cbb7 | ||
|
|
2f097e5dfc | ||
|
|
b71e3241be | ||
|
|
4b018bf421 | ||
|
|
f751de896f | ||
|
|
65aeae9c1e | ||
|
|
80943b0c26 | ||
|
|
a9657ac5c7 | ||
|
|
6fa2e49f3a | ||
|
|
50f0f70805 | ||
|
|
fc95d82c49 | ||
|
|
bb6c1c54ff | ||
|
|
c4414bd910 | ||
|
|
42c911838d | ||
|
|
8917eabfd1 | ||
|
|
fc91469807 | ||
|
|
1735d9c091 | ||
|
|
00d754d466 | ||
|
|
906727f133 | ||
|
|
191d7f11f7 | ||
|
|
54fdc05e3a | ||
|
|
2267569c8d | ||
|
|
2f53ee3974 | ||
|
|
a3b846638f | ||
|
|
cc3033266c | ||
|
|
23b168515c | ||
|
|
049690fea2 | ||
|
|
d3de91de85 | ||
|
|
64eae37788 | ||
|
|
8c71b0b8df | ||
|
|
2bf1ac6b9c | ||
|
|
7197a434c2 | ||
|
|
5349115b94 | ||
|
|
4274e8f3a2 | ||
|
|
7610e159a3 | ||
|
|
9ec94434bb | ||
|
|
1ab444bda2 | ||
|
|
3990ba774f | ||
|
|
1fea9a25a5 | ||
|
|
3f57711c39 | ||
|
|
dc46018757 | ||
|
|
1464e5d5e4 | ||
|
|
95f7ed0de4 | ||
|
|
6e7240026a | ||
|
|
8bae7bf809 | ||
|
|
b78bb2d3d6 | ||
|
|
f9a6402825 | ||
|
|
8ec5f3cb18 | ||
|
|
c330394ff2 | ||
|
|
36e66ad5b4 | ||
|
|
a5a89efc06 | ||
|
|
1078c37043 | ||
|
|
535c7188a8 | ||
|
|
d6f9e216a6 | ||
|
|
7d6a879a67 | ||
|
|
668eac1f9e | ||
|
|
ff99436ce6 | ||
|
|
ec577fc075 | ||
|
|
cc9d51b7be | ||
|
|
cf2b54e8c0 | ||
|
|
a895d1d82a | ||
|
|
11da4188ee | ||
|
|
5b68bad185 | ||
|
|
477e4a7421 | ||
|
|
9a982f05ac | ||
|
|
6959e2cf01 | ||
|
|
f5fe79326a | ||
|
|
f77633cee9 | ||
|
|
c858970573 | ||
|
|
fb3eceaee0 | ||
|
|
8e37fec8f8 | ||
|
|
ccaabfaee8 | ||
|
|
e712af33b7 | ||
|
|
c431fb30c5 | ||
|
|
1c04848480 | ||
|
|
ca1a0797fb | ||
|
|
7b497181fd | ||
|
|
92767b1078 | ||
|
|
7c66dacc40 | ||
|
|
632277f9bd | ||
|
|
3c23ae03f5 | ||
|
|
8117926ca3 | ||
|
|
31e9d27f1a | ||
|
|
0c067d60d8 | ||
|
|
1171d6b165 | ||
|
|
18022a2a48 | ||
|
|
1400b5cca4 | ||
|
|
98b5f9cc29 | ||
|
|
e766cb009e | ||
|
|
993e276858 | ||
|
|
a762fb4df4 | ||
|
|
00781fa459 | ||
|
|
c6e7367a4e | ||
|
|
3749bb1222 | ||
|
|
f3c83b2009 | ||
|
|
d3583c31bc | ||
|
|
2f638d8c0c | ||
|
|
74e43ff65a | ||
|
|
4f0b160ed3 | ||
|
|
709b8696fc | ||
|
|
e080c5d72e | ||
|
|
9cc177a9ad | ||
|
|
9ad4e2525e | ||
|
|
60b5fe0e76 | ||
|
|
9273f97d88 | ||
|
|
e2b7ac68ef | ||
|
|
98a6baf55a | ||
|
|
b5677387f5 | ||
|
|
cd3eaa4762 | ||
|
|
9733bf65de | ||
|
|
bc433f0234 | ||
|
|
ad7283ee6f | ||
|
|
5b941358a9 | ||
|
|
f19e898c57 | ||
|
|
590ffa184d | ||
|
|
46de51512f | ||
|
|
a13f2f26e4 | ||
|
|
c9c26f6f9f | ||
|
|
787c29e42b | ||
|
|
1c0b83d833 | ||
|
|
5c767d09b0 | ||
|
|
75dba51f56 | ||
|
|
78a1827af8 | ||
|
|
9e3733b33b | ||
|
|
4ef65589e8 | ||
|
|
cf9e670309 | ||
|
|
b09f98fbb4 | ||
|
|
e43e010163 | ||
|
|
2a5cee0bd3 | ||
|
|
c962bb79f5 | ||
|
|
add745551b | ||
|
|
7b8213e1f6 | ||
|
|
2e23a33dfc | ||
|
|
bca296ee37 | ||
|
|
2dbd647868 | ||
|
|
2991e7d1fe | ||
|
|
f6f12fc193 | ||
|
|
1fac8c1cea | ||
|
|
590462e2e4 | ||
|
|
578a61d331 | ||
|
|
ed542582f9 | ||
|
|
2574a63975 | ||
|
|
291c55befd | ||
|
|
b7a978505f | ||
|
|
7db1f39473 | ||
|
|
6017578964 | ||
|
|
1dbcd2ac84 | ||
|
|
671ae6558f | ||
|
|
d2174725a9 | ||
|
|
f630c499d5 | ||
|
|
e8f91316ff | ||
|
|
a7cdb095d2 | ||
|
|
cfc29ef821 | ||
|
|
83c6775038 | ||
|
|
ae1b12c120 | ||
|
|
c423de545f | ||
|
|
84011371b7 | ||
|
|
f831ec6b7e | ||
|
|
05bfa9158d | ||
|
|
649016bc85 | ||
|
|
7492b874cf | ||
|
|
9ea21af61f | ||
|
|
dd18e52993 | ||
|
|
7712ee7f35 | ||
|
|
a4a13666e6 | ||
|
|
07ef4da892 | ||
|
|
e9ca4dc9ca | ||
|
|
57bb8fbc45 | ||
|
|
9175624210 | ||
|
|
289a417dfb | ||
|
|
8379a9b275 | ||
|
|
5b1fad118f | ||
|
|
5e8985810e | ||
|
|
d2568e2a29 | ||
|
|
6155634d3b | ||
|
|
667dfd820e | ||
|
|
3e3c526a62 | ||
|
|
c01ce90e06 | ||
|
|
d68c8cb845 | ||
|
|
f5a7db03cd | ||
|
|
88947b9d42 | ||
|
|
3c68ad1302 | ||
|
|
e2b3389da6 | ||
|
|
938385c18b | ||
|
|
5d01b06877 | ||
|
|
f007c093eb | ||
|
|
ee408f7add | ||
|
|
1294d764e2 | ||
|
|
eab1be0e2c | ||
|
|
15dd9d4c93 | ||
|
|
103ecff6a7 | ||
|
|
0182cdda69 | ||
|
|
e91e993fb5 | ||
|
|
e71794d362 | ||
|
|
6fd929160d | ||
|
|
5cdd062ed9 | ||
|
|
0259ed8314 | ||
|
|
d8405e5f81 | ||
|
|
0295d0c9b4 | ||
|
|
8e1e1d56ca | ||
|
|
63bb19d48d | ||
|
|
e92ed13864 |
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"parser": "babel-eslint",
|
||||
"parser": "@babel/eslint-parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 9,
|
||||
"ecmaFeatures": {
|
||||
|
||||
7
.github/workflows/codeql.yml
vendored
7
.github/workflows/codeql.yml
vendored
@@ -1,6 +1,7 @@
|
||||
name: "CodeQL Analysis"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
@@ -21,12 +22,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
17
.github/workflows/master.yml
vendored
17
.github/workflows/master.yml
vendored
@@ -1,6 +1,7 @@
|
||||
name: "Master Build, Test & Deploy"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
@@ -9,12 +10,12 @@ jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set node version
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '10.x'
|
||||
node-version: '18.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()
|
||||
@@ -36,9 +37,9 @@ jobs:
|
||||
- name: Generate sitemap
|
||||
run: npx grunt exec:sitemap
|
||||
|
||||
- name: UI Tests
|
||||
if: success()
|
||||
run: xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
|
||||
# - name: UI Tests
|
||||
# if: success()
|
||||
# run: xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
|
||||
|
||||
- name: Prepare for GitHub Pages
|
||||
if: success()
|
||||
@@ -46,7 +47,7 @@ jobs:
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
if: success() && github.ref == 'refs/heads/master'
|
||||
uses: crazy-max/ghaction-github-pages@v2
|
||||
uses: crazy-max/ghaction-github-pages@v3
|
||||
with:
|
||||
target_branch: gh-pages
|
||||
build_dir: ./build/prod
|
||||
|
||||
15
.github/workflows/pull_requests.yml
vendored
15
.github/workflows/pull_requests.yml
vendored
@@ -1,6 +1,7 @@
|
||||
name: "Pull Requests"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
types: [synchronize, opened, reopened]
|
||||
|
||||
@@ -8,12 +9,12 @@ jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set node version
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '10.x'
|
||||
node-version: '18.x'
|
||||
|
||||
- name: Install
|
||||
run: |
|
||||
@@ -26,12 +27,12 @@ jobs:
|
||||
- name: Unit Tests
|
||||
run: |
|
||||
npm test
|
||||
npx grunt testnodeconsumer
|
||||
npm run testnodeconsumer
|
||||
|
||||
- name: Production Build
|
||||
if: success()
|
||||
run: npx grunt prod
|
||||
|
||||
- name: UI Tests
|
||||
if: success()
|
||||
run: xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
|
||||
# - name: UI Tests
|
||||
# if: success()
|
||||
# run: xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
|
||||
|
||||
15
.github/workflows/releases.yml
vendored
15
.github/workflows/releases.yml
vendored
@@ -1,6 +1,7 @@
|
||||
name: "Releases"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
@@ -9,12 +10,12 @@ jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set node version
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '10.x'
|
||||
node-version: '18.x'
|
||||
|
||||
- name: Install
|
||||
run: |
|
||||
@@ -27,15 +28,15 @@ jobs:
|
||||
- name: Unit Tests
|
||||
run: |
|
||||
npm test
|
||||
npx grunt testnodeconsumer
|
||||
npm run testnodeconsumer
|
||||
|
||||
- name: Production Build
|
||||
if: success()
|
||||
run: npx grunt prod
|
||||
|
||||
- name: UI Tests
|
||||
if: success()
|
||||
run: xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
|
||||
# - name: UI Tests
|
||||
# if: success()
|
||||
# run: xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
|
||||
|
||||
- name: Upload Release Assets
|
||||
if: success()
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -11,4 +11,4 @@ src/node/config/OperationConfig.json
|
||||
src/node/index.mjs
|
||||
**/*.DS_Store
|
||||
tests/browser/output/*
|
||||
|
||||
.node-version
|
||||
|
||||
137
CHANGELOG.md
137
CHANGELOG.md
@@ -13,8 +13,79 @@ All major and minor version changes will be documented in this file. Details of
|
||||
|
||||
## Details
|
||||
|
||||
### [9.52.0] - 2022-11-25
|
||||
- Added 'ChaCha' operation [@joostrijneveld] | [#1466]
|
||||
|
||||
### [9.51.0] - 2022-11-25
|
||||
- Added 'CMAC' operation [@mikecat] | [#1457]
|
||||
|
||||
### [9.50.0] - 2022-11-25
|
||||
- Added 'Shuffle' operation [@mikecat] | [#1472]
|
||||
|
||||
### [9.49.0] - 2022-11-11
|
||||
- Added 'LZ4 Compress' and 'LZ4 Decompress' operations [@n1474335] | [31a7f83]
|
||||
|
||||
### [9.48.0] - 2022-10-14
|
||||
- Added 'LM Hash' and 'NT Hash' operations [@n1474335] [@brun0ne] | [#1427]
|
||||
|
||||
### [9.47.0] - 2022-10-14
|
||||
- Added 'LZMA Decompress' and 'LZMA Compress' operations [@mattnotmitt] | [#1421]
|
||||
|
||||
### [9.46.0] - 2022-07-08
|
||||
- Added 'Cetacean Cipher Encode' and 'Cetacean Cipher Decode' operations [@valdelaseras] | [#1308]
|
||||
|
||||
### [9.45.0] - 2022-07-08
|
||||
- Added 'ROT8000' operation [@thomasleplus] | [#1250]
|
||||
|
||||
### [9.44.0] - 2022-07-08
|
||||
- Added 'LZString Compress' and 'LZString Decompress' operations [@crespyl] | [#1266]
|
||||
|
||||
### [9.43.0] - 2022-07-08
|
||||
- Added 'ROT13 Brute Force' and 'ROT47 Brute Force' operations [@mikecat] | [#1264]
|
||||
|
||||
### [9.42.0] - 2022-07-08
|
||||
- Added 'LS47 Encrypt' and 'LS47 Decrypt' operations [@n1073645] | [#951]
|
||||
|
||||
### [9.41.0] - 2022-07-08
|
||||
- Added 'Caesar Box Cipher' operation [@n1073645] | [#1066]
|
||||
|
||||
### [9.40.0] - 2022-07-08
|
||||
- Added 'P-list Viewer' operation [@n1073645] | [#906]
|
||||
|
||||
### [9.39.0] - 2022-06-09
|
||||
- Added 'ELF Info' operation [@n1073645] | [#1364]
|
||||
|
||||
### [9.38.0] - 2022-05-30
|
||||
- Added 'Parse TCP' operation [@n1474335] | [a895d1d]
|
||||
|
||||
### [9.37.0] - 2022-03-29
|
||||
- 'SM4 Encrypt' and 'SM4 Decrypt' operations added [@swesven] | [#1189]
|
||||
- NoPadding options added for CBC and ECB modes in AES, DES and Triple DES Decrypt operations [@swesven] | [#1189]
|
||||
|
||||
### [9.36.0] - 2022-03-29
|
||||
- 'SIGABA' operation added [@hettysymes] | [#934]
|
||||
|
||||
### [9.35.0] - 2022-03-28
|
||||
- 'To Base45' and 'From Base45' operations added [@t-8ch] | [#1242]
|
||||
|
||||
### [9.34.0] - 2022-03-28
|
||||
- 'Get All Casings' operation added [@n1073645] | [#1065]
|
||||
|
||||
### [9.33.0] - 2022-03-25
|
||||
- Updated to support Node 17 [@n1474335] [@john19696] [@t-8ch] | [[#1326] [#1313] [#1244]
|
||||
- Improved CJS and ESM module support [@d98762625] | [#1037]
|
||||
|
||||
### [9.32.0] - 2021-08-18
|
||||
- 'Protobuf Encode' operation added and decode operation modified to allow decoding with full and partial schemas [@n1474335] | [dd18e52]
|
||||
|
||||
### [9.31.0] - 2021-08-10
|
||||
- 'HASSH Client Fingerprint' and 'HASSH Server Fingerprint' operations added [@n1474335] | [e9ca4dc]
|
||||
|
||||
### [9.30.0] - 2021-08-10
|
||||
- 'JA3S Fingerprint' operation added [@n1474335] | [289a417]
|
||||
|
||||
### [9.29.0] - 2021-07-28
|
||||
- 'TLS JA3 Fingerprint' operation added [@n1474335] | [9a33498]
|
||||
- 'JA3 Fingerprint' operation added [@n1474335] | [9a33498]
|
||||
|
||||
### [9.28.0] - 2021-03-26
|
||||
- 'CBOR Encode' and 'CBOR Decode' operations added [@Danh4] | [#999]
|
||||
@@ -109,7 +180,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]
|
||||
|
||||
@@ -262,6 +333,29 @@ All major and minor version changes will be documented in this file. Details of
|
||||
|
||||
|
||||
|
||||
[9.52.0]: https://github.com/gchq/CyberChef/releases/tag/v9.52.0
|
||||
[9.51.0]: https://github.com/gchq/CyberChef/releases/tag/v9.51.0
|
||||
[9.50.0]: https://github.com/gchq/CyberChef/releases/tag/v9.50.0
|
||||
[9.49.0]: https://github.com/gchq/CyberChef/releases/tag/v9.49.0
|
||||
[9.48.0]: https://github.com/gchq/CyberChef/releases/tag/v9.48.0
|
||||
[9.47.0]: https://github.com/gchq/CyberChef/releases/tag/v9.47.0
|
||||
[9.46.0]: https://github.com/gchq/CyberChef/releases/tag/v9.46.0
|
||||
[9.45.0]: https://github.com/gchq/CyberChef/releases/tag/v9.45.0
|
||||
[9.44.0]: https://github.com/gchq/CyberChef/releases/tag/v9.44.0
|
||||
[9.43.0]: https://github.com/gchq/CyberChef/releases/tag/v9.43.0
|
||||
[9.42.0]: https://github.com/gchq/CyberChef/releases/tag/v9.42.0
|
||||
[9.41.0]: https://github.com/gchq/CyberChef/releases/tag/v9.41.0
|
||||
[9.40.0]: https://github.com/gchq/CyberChef/releases/tag/v9.40.0
|
||||
[9.39.0]: https://github.com/gchq/CyberChef/releases/tag/v9.39.0
|
||||
[9.38.0]: https://github.com/gchq/CyberChef/releases/tag/v9.38.0
|
||||
[9.37.0]: https://github.com/gchq/CyberChef/releases/tag/v9.37.0
|
||||
[9.36.0]: https://github.com/gchq/CyberChef/releases/tag/v9.36.0
|
||||
[9.35.0]: https://github.com/gchq/CyberChef/releases/tag/v9.35.0
|
||||
[9.34.0]: https://github.com/gchq/CyberChef/releases/tag/v9.34.0
|
||||
[9.33.0]: https://github.com/gchq/CyberChef/releases/tag/v9.33.0
|
||||
[9.32.0]: https://github.com/gchq/CyberChef/releases/tag/v9.32.0
|
||||
[9.31.0]: https://github.com/gchq/CyberChef/releases/tag/v9.31.0
|
||||
[9.30.0]: https://github.com/gchq/CyberChef/releases/tag/v9.30.0
|
||||
[9.29.0]: https://github.com/gchq/CyberChef/releases/tag/v9.29.0
|
||||
[9.28.0]: https://github.com/gchq/CyberChef/releases/tag/v9.28.0
|
||||
[9.27.0]: https://github.com/gchq/CyberChef/releases/tag/v9.27.0
|
||||
@@ -372,9 +466,24 @@ 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
|
||||
[@mikecat]: https://github.com/mikecat
|
||||
[@crespyl]: https://github.com/crespyl
|
||||
[@thomasleplus]: https://github.com/thomasleplus
|
||||
[@valdelaseras]: https://github.com/valdelaseras
|
||||
[@brun0ne]: https://github.com/brun0ne
|
||||
[@joostrijneveld]: https://github.com/joostrijneveld
|
||||
|
||||
[8ad18b]: https://github.com/gchq/CyberChef/commit/8ad18bc7db6d9ff184ba3518686293a7685bf7b7
|
||||
[9a33498]: https://github.com/gchq/CyberChef/commit/9a33498fed26a8df9c9f35f39a78a174bf50a513
|
||||
[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
|
||||
[31a7f83]: https://github.com/gchq/CyberChef/commit/31a7f83b82e78927f89689f323fcb9185144d6ff
|
||||
|
||||
[#95]: https://github.com/gchq/CyberChef/pull/299
|
||||
[#173]: https://github.com/gchq/CyberChef/pull/173
|
||||
@@ -441,9 +550,12 @@ All major and minor version changes will be documented in this file. Details of
|
||||
[#674]: https://github.com/gchq/CyberChef/pull/674
|
||||
[#683]: https://github.com/gchq/CyberChef/pull/683
|
||||
[#865]: https://github.com/gchq/CyberChef/pull/865
|
||||
[#906]: https://github.com/gchq/CyberChef/pull/906
|
||||
[#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
|
||||
[#951]: https://github.com/gchq/CyberChef/pull/951
|
||||
[#952]: https://github.com/gchq/CyberChef/pull/952
|
||||
[#965]: https://github.com/gchq/CyberChef/pull/965
|
||||
[#966]: https://github.com/gchq/CyberChef/pull/966
|
||||
@@ -451,6 +563,25 @@ 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
|
||||
[#1066]: https://github.com/gchq/CyberChef/pull/1066
|
||||
[#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
|
||||
[#1364]: https://github.com/gchq/CyberChef/pull/1364
|
||||
[#1264]: https://github.com/gchq/CyberChef/pull/1264
|
||||
[#1266]: https://github.com/gchq/CyberChef/pull/1266
|
||||
[#1250]: https://github.com/gchq/CyberChef/pull/1250
|
||||
[#1308]: https://github.com/gchq/CyberChef/pull/1308
|
||||
[#1421]: https://github.com/gchq/CyberChef/pull/1421
|
||||
[#1427]: https://github.com/gchq/CyberChef/pull/1427
|
||||
[#1472]: https://github.com/gchq/CyberChef/pull/1472
|
||||
[#1457]: https://github.com/gchq/CyberChef/pull/1457
|
||||
[#1466]: https://github.com/gchq/CyberChef/pull/1466
|
||||
|
||||
|
||||
96
Gruntfile.js
96
Gruntfile.js
@@ -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,
|
||||
},
|
||||
|
||||
17
README.md
17
README.md
@@ -2,7 +2,6 @@
|
||||
|
||||
[](https://github.com/gchq/CyberChef/actions?query=workflow%3A%22Master+Build%2C+Test+%26+Deploy%22)
|
||||
[](https://lgtm.com/projects/g/gchq/CyberChef/context:javascript)
|
||||
[](https://david-dm.org/gchq/CyberChef)
|
||||
[](https://www.npmjs.com/package/cyberchef)
|
||||
[](https://github.com/gchq/CyberChef/blob/master/LICENSE)
|
||||
[](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.
|
||||
|
||||
@@ -55,7 +54,7 @@ You can use as many operations as you like in simple or complex ways. Some examp
|
||||
- Whenever you modify the input or the recipe, CyberChef will automatically "bake" for you and produce the output immediately.
|
||||
- This can be turned off and operated manually if it is affecting performance (if the input is very large, for instance).
|
||||
- Automated encoding detection
|
||||
- CyberChef uses [a number of techniques](https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic) to attempt to automatically detect which encodings your data is under. If it finds a suitable operation which can make sense of your data, it displays the 'magic' icon in the Output field which you can click to decode your data.
|
||||
- CyberChef uses [a number of techniques](https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic) to attempt to automatically detect which encodings your data is under. If it finds a suitable operation that make sense of your data, it displays the 'magic' icon in the Output field which you can click to decode your data.
|
||||
- Breakpoints
|
||||
- You can set breakpoints on any operation in your recipe to pause execution before running it.
|
||||
- You can also step through the recipe one operation at a time to see what the data looks like at each stage.
|
||||
@@ -67,7 +66,7 @@ You can use as many operations as you like in simple or complex ways. Some examp
|
||||
- Highlighting
|
||||
- When you highlight text in the input or output, the offset and length values will be displayed and, if possible, the corresponding data will be highlighted in the output or input respectively (example: [highlight the word 'question' in the input to see where it appears in the output][11]).
|
||||
- Save to file and load from file
|
||||
- You can save the output to a file at any time or load a file by dragging and dropping it into the input field. Files up to around 2GB are supported (depending on your browser), however some operations may take a very long time to run over this much data.
|
||||
- You can save the output to a file at any time or load a file by dragging and dropping it into the input field. Files up to around 2GB are supported (depending on your browser), however, some operations may take a very long time to run over this much data.
|
||||
- CyberChef is entirely client-side
|
||||
- It should be noted that none of your recipe configuration or input (either text or files) is ever sent to the CyberChef web server - all processing is carried out within your browser, on your own computer.
|
||||
- Due to this feature, CyberChef can be downloaded and run locally. You can use the link in the top left corner of the app to download a full copy of CyberChef and drop it into a virtual machine, share it with other people, or host it in a closed network.
|
||||
@@ -75,10 +74,10 @@ You can use as many operations as you like in simple or complex ways. Some examp
|
||||
|
||||
## Deep linking
|
||||
|
||||
By manipulation of CyberChef's URL hash, you can change the initial settings with which the page opens.
|
||||
By manipulating CyberChef's URL hash, you can change the initial settings with which the page opens.
|
||||
The format is `https://gchq.github.io/CyberChef/#recipe=Operation()&input=...`
|
||||
|
||||
Supported arguments are `recipe`, `input` (encoded in Base64), and `theme`.
|
||||
Supported arguments are `recipe`, `input` (encoded in Base64), and `theme`.
|
||||
|
||||
|
||||
## Browser support
|
||||
@@ -91,12 +90,12 @@ CyberChef is built to support
|
||||
|
||||
## Node.js support
|
||||
|
||||
CyberChef is built to fully support Node.js `v10` and partially supports `v12`. Named imports using a deep import specifier does not work in `v12`. For more information, see the Node API page in the project [wiki pages](https://github.com/gchq/CyberChef/wiki)
|
||||
CyberChef is built to fully support Node.js `v16`. For more information, see the Node API page in the project [wiki pages](https://github.com/gchq/CyberChef/wiki)
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributing a new operation to CyberChef is super easy! There is a quickstart script which will walk you through the process. If you can write basic JavaScript, you can write a CyberChef operation.
|
||||
Contributing a new operation to CyberChef is super easy! The quickstart script will walk you through the process. If you can write basic JavaScript, you can write a CyberChef operation.
|
||||
|
||||
An installation walkthrough, how-to guides for adding new operations and themes, descriptions of the repository structure, available data types and coding conventions can all be found in the project [wiki pages](https://github.com/gchq/CyberChef/wiki).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -11,6 +11,7 @@ module.exports = function(api) {
|
||||
],
|
||||
"plugins": [
|
||||
"dynamic-import-node",
|
||||
"@babel/plugin-syntax-import-assertions",
|
||||
[
|
||||
"babel-plugin-transform-builtin-extend", {
|
||||
"globals": ["Error"]
|
||||
|
||||
@@ -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": {
|
||||
|
||||
27339
package-lock.json
generated
27339
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
167
package.json
167
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cyberchef",
|
||||
"version": "9.29.1",
|
||||
"version": "9.52.0",
|
||||
"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,154 +27,159 @@
|
||||
"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": "^103.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.5.1",
|
||||
"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",
|
||||
"webpack-node-externals": "^2.5.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",
|
||||
"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",
|
||||
"@blu3r4y/lzma": "^2.3.3",
|
||||
"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",
|
||||
"jsonpath": "^1.1.0",
|
||||
"json5": "^2.2.1",
|
||||
"jsonpath-plus": "^7.2.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jsqr": "^1.3.1",
|
||||
"jsrsasign": "^10.1.10",
|
||||
"jsqr": "^1.4.0",
|
||||
"jsrsasign": "^10.5.23",
|
||||
"kbpgp": "2.1.15",
|
||||
"libbzip2-wasm": "0.0.4",
|
||||
"libyara-wasm": "^1.1.0",
|
||||
"lodash": "^4.17.20",
|
||||
"loglevel": "^1.7.1",
|
||||
"libyara-wasm": "^1.2.1",
|
||||
"lodash": "^4.17.21",
|
||||
"loglevel": "^1.8.0",
|
||||
"loglevel-message-prefix": "^3.0.0",
|
||||
"markdown-it": "^12.0.4",
|
||||
"moment": "^2.29.1",
|
||||
"moment-timezone": "^0.5.33",
|
||||
"lz-string": "^1.4.4",
|
||||
"lz4js": "^0.2.0",
|
||||
"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",
|
||||
"nodom": "^2.4.0",
|
||||
"notepack.io": "^2.3.0",
|
||||
"notepack.io": "^3.0.1",
|
||||
"ntlm": "^0.1.3",
|
||||
"nwmatcher": "^1.4.4",
|
||||
"otp": "0.1.3",
|
||||
"path": "^0.12.7",
|
||||
"popper.js": "^1.16.1",
|
||||
"process": "^0.11.10",
|
||||
"protobufjs": "^6.11.3",
|
||||
"qr-image": "^3.2.0",
|
||||
"scryptsy": "^2.1.0",
|
||||
"snackbarjs": "^1.1.0",
|
||||
"sortablejs": "^1.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": "3.0.2",
|
||||
"ua-parser-js": "^1.0.2",
|
||||
"unorm": "^1.6.0",
|
||||
"utf8": "^3.0.0",
|
||||
"vkbeautify": "^0.99.3",
|
||||
"xmldom": "^0.4.0",
|
||||
"xmldom": "^0.6.0",
|
||||
"xpath": "0.0.32",
|
||||
"xregexp": "^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",
|
||||
"node": "npx grunt node",
|
||||
"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 --openssl-legacy-provider tests/node/index.mjs && node --experimental-modules --experimental-json-modules --no-warnings --no-deprecation --openssl-legacy-provider 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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -206,7 +206,7 @@ class Utils {
|
||||
* Utils.parseEscapedChars("\\n");
|
||||
*/
|
||||
static parseEscapedChars(str) {
|
||||
return str.replace(/\\([bfnrtv'"]|[0-3][0-7]{2}|[0-7]{1,2}|x[\da-fA-F]{2}|u[\da-fA-F]{4}|u\{[\da-fA-F]{1,6}\}|\\)/g, function(m, a) {
|
||||
return str.replace(/\\([abfnrtv'"]|[0-3][0-7]{2}|[0-7]{1,2}|x[\da-fA-F]{2}|u[\da-fA-F]{4}|u\{[\da-fA-F]{1,6}\}|\\)/g, function(m, a) {
|
||||
switch (a[0]) {
|
||||
case "\\":
|
||||
return "\\";
|
||||
@@ -219,6 +219,8 @@ class Utils {
|
||||
case "6":
|
||||
case "7":
|
||||
return String.fromCharCode(parseInt(a, 8));
|
||||
case "a":
|
||||
return String.fromCharCode(7);
|
||||
case "b":
|
||||
return "\b";
|
||||
case "t":
|
||||
@@ -380,6 +382,70 @@ class Utils {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts a byte array to an integer.
|
||||
*
|
||||
* @param {byteArray} byteArray
|
||||
* @param {string} byteorder - "little" or "big"
|
||||
* @returns {integer}
|
||||
*
|
||||
* @example
|
||||
* // returns 67305985
|
||||
* Utils.byteArrayToInt([1, 2, 3, 4], "little");
|
||||
*
|
||||
* // returns 16909060
|
||||
* Utils.byteArrayToInt([1, 2, 3, 4], "big");
|
||||
*/
|
||||
static byteArrayToInt(byteArray, byteorder) {
|
||||
let value = 0;
|
||||
if (byteorder === "big") {
|
||||
for (let i = 0; i < byteArray.length; i++) {
|
||||
value = (value * 256) + byteArray[i];
|
||||
}
|
||||
} else {
|
||||
for (let i = byteArray.length - 1; i >= 0; i--) {
|
||||
value = (value * 256) + byteArray[i];
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts an integer to a byte array of {length} bytes.
|
||||
*
|
||||
* @param {integer} value
|
||||
* @param {integer} length
|
||||
* @param {string} byteorder - "little" or "big"
|
||||
* @returns {byteArray}
|
||||
*
|
||||
* @example
|
||||
* // returns [5, 255, 109, 1]
|
||||
* Utils.intToByteArray(23985925, 4, "little");
|
||||
*
|
||||
* // returns [1, 109, 255, 5]
|
||||
* Utils.intToByteArray(23985925, 4, "big");
|
||||
*
|
||||
* // returns [0, 0, 0, 0, 1, 109, 255, 5]
|
||||
* Utils.intToByteArray(23985925, 8, "big");
|
||||
*/
|
||||
static intToByteArray(value, length, byteorder) {
|
||||
const arr = new Array(length);
|
||||
if (byteorder === "little") {
|
||||
for (let i = 0; i < length; i++) {
|
||||
arr[i] = value & 0xFF;
|
||||
value = value >>> 8;
|
||||
}
|
||||
} else {
|
||||
for (let i = length - 1; i >= 0; i--) {
|
||||
arr[i] = value & 0xFF;
|
||||
value = value >>> 8;
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts a string to an ArrayBuffer.
|
||||
* Treats the string as UTF-8 if any values are over 255.
|
||||
@@ -723,7 +789,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 +1273,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
43
src/core/config/Categories.json
Executable file → Normal file
43
src/core/config/Categories.json
Executable file → Normal file
@@ -20,6 +20,8 @@
|
||||
"From Octal",
|
||||
"To Base32",
|
||||
"From Base32",
|
||||
"To Base45",
|
||||
"From Base45",
|
||||
"To Base58",
|
||||
"From Base58",
|
||||
"To Base62",
|
||||
@@ -73,16 +75,24 @@
|
||||
"AES Decrypt",
|
||||
"Blowfish Encrypt",
|
||||
"Blowfish Decrypt",
|
||||
"ChaCha",
|
||||
"DES Encrypt",
|
||||
"DES Decrypt",
|
||||
"Triple DES Encrypt",
|
||||
"Triple DES Decrypt",
|
||||
"LS47 Encrypt",
|
||||
"LS47 Decrypt",
|
||||
"RC2 Encrypt",
|
||||
"RC2 Decrypt",
|
||||
"RC4",
|
||||
"RC4 Drop",
|
||||
"SM4 Encrypt",
|
||||
"SM4 Decrypt",
|
||||
"ROT13",
|
||||
"ROT13 Brute Force",
|
||||
"ROT47",
|
||||
"ROT47 Brute Force",
|
||||
"ROT8000",
|
||||
"XOR",
|
||||
"XOR Brute Force",
|
||||
"Vigenère Encode",
|
||||
@@ -93,6 +103,7 @@
|
||||
"Bacon Cipher Decode",
|
||||
"Bifid Cipher Encode",
|
||||
"Bifid Cipher Decode",
|
||||
"Caesar Box Cipher",
|
||||
"Affine Cipher Encode",
|
||||
"Affine Cipher Decode",
|
||||
"A1Z26 Cipher Encode",
|
||||
@@ -102,6 +113,8 @@
|
||||
"Atbash Cipher",
|
||||
"CipherSaber2 Encrypt",
|
||||
"CipherSaber2 Decrypt",
|
||||
"Cetacean Cipher Encode",
|
||||
"Cetacean Cipher Decode",
|
||||
"Substitute",
|
||||
"Derive PBKDF2 key",
|
||||
"Derive EVP key",
|
||||
@@ -118,7 +131,8 @@
|
||||
"Multiple Bombe",
|
||||
"Typex",
|
||||
"Lorenz",
|
||||
"Colossus"
|
||||
"Colossus",
|
||||
"SIGABA"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -171,7 +185,8 @@
|
||||
"Bit shift right",
|
||||
"Rotate left",
|
||||
"Rotate right",
|
||||
"ROT13"
|
||||
"ROT13",
|
||||
"ROT8000"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -185,15 +200,20 @@
|
||||
"Parse IP range",
|
||||
"Parse IPv6 address",
|
||||
"Parse IPv4 header",
|
||||
"Parse TCP",
|
||||
"Parse UDP",
|
||||
"Parse SSH Host Key",
|
||||
"Parse URI",
|
||||
"URL Encode",
|
||||
"URL Decode",
|
||||
"Protobuf Decode",
|
||||
"Protobuf Encode",
|
||||
"VarInt Encode",
|
||||
"VarInt Decode",
|
||||
"TLS JA3 Fingerprint",
|
||||
"JA3 Fingerprint",
|
||||
"JA3S Fingerprint",
|
||||
"HASSH Client Fingerprint",
|
||||
"HASSH Server Fingerprint",
|
||||
"Format MAC addresses",
|
||||
"Change IP format",
|
||||
"Group IP addresses",
|
||||
@@ -226,9 +246,11 @@
|
||||
"From Case Insensitive Regex",
|
||||
"Add line numbers",
|
||||
"Remove line numbers",
|
||||
"Get All Casings",
|
||||
"To Table",
|
||||
"Reverse",
|
||||
"Sort",
|
||||
"Shuffle",
|
||||
"Unique",
|
||||
"Split",
|
||||
"Filter",
|
||||
@@ -309,7 +331,13 @@
|
||||
"Bzip2 Decompress",
|
||||
"Bzip2 Compress",
|
||||
"Tar",
|
||||
"Untar"
|
||||
"Untar",
|
||||
"LZString Decompress",
|
||||
"LZString Compress",
|
||||
"LZMA Decompress",
|
||||
"LZMA Compress",
|
||||
"LZ4 Decompress",
|
||||
"LZ4 Compress"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -341,10 +369,13 @@
|
||||
"Compare SSDEEP hashes",
|
||||
"Compare CTPH hashes",
|
||||
"HMAC",
|
||||
"CMAC",
|
||||
"Bcrypt",
|
||||
"Bcrypt compare",
|
||||
"Bcrypt parse",
|
||||
"Scrypt",
|
||||
"NT Hash",
|
||||
"LM Hash",
|
||||
"Fletcher-8 Checksum",
|
||||
"Fletcher-16 Checksum",
|
||||
"Fletcher-32 Checksum",
|
||||
@@ -402,7 +433,8 @@
|
||||
"Extract RGBA",
|
||||
"View Bit Plane",
|
||||
"Randomize Colour Palette",
|
||||
"Extract LSB"
|
||||
"Extract LSB",
|
||||
"ELF Info"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -445,6 +477,7 @@
|
||||
"Frequency distribution",
|
||||
"Index of Coincidence",
|
||||
"Chi Square",
|
||||
"P-list Viewer",
|
||||
"Disassemble x86",
|
||||
"Pseudo-Random Number Generator",
|
||||
"Generate UUID",
|
||||
|
||||
144
src/core/config/scripts/newMinorVersion.mjs
Normal file
144
src/core/config/scripts/newMinorVersion.mjs
Normal 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\nCommit changes and then run `npm version minor`.");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
getFeature();
|
||||
@@ -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
27
src/core/lib/Base45.mjs
Normal 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 $%*+\\-./:";
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* Base85 resources.
|
||||
*
|
||||
@@ -32,13 +34,12 @@ export const ALPHABET_OPTIONS = [
|
||||
* @returns {string}
|
||||
*/
|
||||
export function alphabetName(alphabet) {
|
||||
alphabet = alphabet.replace(/'/g, "'");
|
||||
alphabet = alphabet.replace(/"/g, """);
|
||||
alphabet = alphabet.replace(/\\/g, "\");
|
||||
alphabet = escape(alphabet);
|
||||
let name;
|
||||
|
||||
ALPHABET_OPTIONS.forEach(function(a) {
|
||||
if (escape(alphabet) === escape(a.value)) name = a.name;
|
||||
const expanded = Utils.expandAlphRange(a.value).join("");
|
||||
if (alphabet === escape(expanded)) name = a.name;
|
||||
});
|
||||
|
||||
return name;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ export const FILE_SIGNATURES = {
|
||||
10: 0x42,
|
||||
11: 0x50
|
||||
},
|
||||
extractor: null
|
||||
extractor: extractWEBP
|
||||
},
|
||||
{
|
||||
name: "Camera Image File Format",
|
||||
@@ -3032,6 +3032,30 @@ export function extractPNG(bytes, offset) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* WEBP extractor.
|
||||
*
|
||||
* @param {Uint8Array} bytes
|
||||
* @param {number} offset
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
export function extractWEBP(bytes, offset) {
|
||||
const stream = new Stream(bytes.slice(offset));
|
||||
|
||||
// Move to file size offset.
|
||||
stream.moveForwardsBy(4);
|
||||
|
||||
// Read file size field.
|
||||
const fileSize = stream.readInt(4, "le");
|
||||
|
||||
// Move to end of file.
|
||||
// There is no need to minus 8 from the size as the size factors in the offset.
|
||||
stream.moveForwardsBy(fileSize);
|
||||
|
||||
return stream.carve();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* BMP extractor.
|
||||
*
|
||||
@@ -3778,8 +3802,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 +3822,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 +3843,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 +3910,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 +3972,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;
|
||||
|
||||
|
||||
@@ -105,13 +105,17 @@ export function fromHex(data, delim="Auto", byteLen=2) {
|
||||
throw new OperationError("Byte length must be a positive integer");
|
||||
|
||||
if (delim !== "None") {
|
||||
const delimRegex = delim === "Auto" ? /[^a-f\d]|(0x)/gi : Utils.regexRep(delim);
|
||||
data = data.replace(delimRegex, "");
|
||||
const delimRegex = delim === "Auto" ? /[^a-f\d]|0x/gi : Utils.regexRep(delim);
|
||||
data = data.split(delimRegex);
|
||||
} else {
|
||||
data = [data];
|
||||
}
|
||||
|
||||
const output = [];
|
||||
for (let i = 0; i < data.length; i += byteLen) {
|
||||
output.push(parseInt(data.substr(i, byteLen), 16));
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
for (let j = 0; j < data[i].length; j += byteLen) {
|
||||
output.push(parseInt(data[i].substr(j, byteLen), 16));
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
244
src/core/lib/LS47.mjs
Normal file
244
src/core/lib/LS47.mjs
Normal file
@@ -0,0 +1,244 @@
|
||||
/**
|
||||
* @author n1073645 [n1073645@gmail.com]
|
||||
* @copyright Crown Copyright 2020
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
const letters = "_abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()";
|
||||
const tiles = [];
|
||||
|
||||
/**
|
||||
* Initialises the tiles with values and positions.
|
||||
*/
|
||||
export function initTiles() {
|
||||
for (let i = 0; i < 49; i++)
|
||||
tiles.push([letters.charAt(i), [Math.floor(i/7), i % 7]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates the key "down".
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {number} col
|
||||
* @param {number} n
|
||||
* @returns {string}
|
||||
*/
|
||||
function rotateDown(key, col, n) {
|
||||
const lines = [];
|
||||
for (let i = 0; i < 7; i++)
|
||||
lines.push(key.slice(i*7, (i + 1) * 7));
|
||||
const lefts = [];
|
||||
let mids = [];
|
||||
const rights = [];
|
||||
lines.forEach((element) => {
|
||||
lefts.push(element.slice(0, col));
|
||||
mids.push(element.charAt(col));
|
||||
rights.push(element.slice(col+1));
|
||||
});
|
||||
n = (7 - n % 7) % 7;
|
||||
mids = mids.slice(n).concat(mids.slice(0, n));
|
||||
let result = "";
|
||||
for (let i = 0; i < 7; i++)
|
||||
result += lefts[i] + mids[i] + rights[i];
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates the key "right".
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {number} row
|
||||
* @param {number} n
|
||||
* @returns {string}
|
||||
*/
|
||||
function rotateRight(key, row, n) {
|
||||
const mid = key.slice(row * 7, (row + 1) * 7);
|
||||
n = (7 - n % 7) % 7;
|
||||
return key.slice(0, 7 * row) + mid.slice(n) + mid.slice(0, n) + key.slice(7 * (row + 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the position of a letter in the tiles.
|
||||
*
|
||||
* @param {string} letter
|
||||
* @returns {string}
|
||||
*/
|
||||
function findIx(letter) {
|
||||
for (let i = 0; i < tiles.length; i++)
|
||||
if (tiles[i][0] === letter)
|
||||
return tiles[i][1];
|
||||
throw new OperationError("Letter " + letter + " is not included in LS47");
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives key from the input password.
|
||||
*
|
||||
* @param {string} password
|
||||
* @returns {string}
|
||||
*/
|
||||
export function deriveKey(password) {
|
||||
let i = 0;
|
||||
let k = letters;
|
||||
for (const c of password) {
|
||||
const [row, col] = findIx(c);
|
||||
k = rotateDown(rotateRight(k, i, col), i, row);
|
||||
i = (i + 1) % 7;
|
||||
}
|
||||
return k;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the key is a valid key.
|
||||
*
|
||||
* @param {string} key
|
||||
*/
|
||||
function checkKey(key) {
|
||||
if (key.length !== letters.length)
|
||||
throw new OperationError("Wrong key size");
|
||||
const counts = new Array();
|
||||
for (let i = 0; i < letters.length; i++)
|
||||
counts[letters.charAt(i)] = 0;
|
||||
for (const elem of letters) {
|
||||
if (letters.indexOf(elem) === -1)
|
||||
throw new OperationError("Letter " + elem + " not in LS47");
|
||||
counts[elem]++;
|
||||
if (counts[elem] > 1)
|
||||
throw new OperationError("Letter duplicated in the key");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the position of a letter in they key.
|
||||
*
|
||||
* @param {letter} key
|
||||
* @param {string} letter
|
||||
* @returns {object}
|
||||
*/
|
||||
function findPos (key, letter) {
|
||||
const index = key.indexOf(letter);
|
||||
if (index >= 0 && index < 49)
|
||||
return [Math.floor(index/7), index%7];
|
||||
throw new OperationError("Letter " + letter + " is not in the key");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the character at the position on the tiles.
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {object} coord
|
||||
* @returns {string}
|
||||
*/
|
||||
function findAtPos(key, coord) {
|
||||
return key.charAt(coord[1] + (coord[0] * 7));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns new position by adding two positions.
|
||||
*
|
||||
* @param {object} a
|
||||
* @param {object} b
|
||||
* @returns {object}
|
||||
*/
|
||||
function addPos(a, b) {
|
||||
return [(a[0] + b[0]) % 7, (a[1] + b[1]) % 7];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns new position by subtracting two positions.
|
||||
* Note: We have to manually do the remainder division, since JS does not
|
||||
* operate correctly on negative numbers (e.g. -3 % 4 = -3 when it should be 1).
|
||||
*
|
||||
* @param {object} a
|
||||
* @param {object} b
|
||||
* @returns {object}
|
||||
*/
|
||||
function subPos(a, b) {
|
||||
const asub = a[0] - b[0];
|
||||
const bsub = a[1] - b[1];
|
||||
return [asub - (Math.floor(asub/7) * 7), bsub - (Math.floor(bsub/7) * 7)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts the plaintext string.
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} plaintext
|
||||
* @returns {string}
|
||||
*/
|
||||
function encrypt(key, plaintext) {
|
||||
checkKey(key);
|
||||
let mp = [0, 0];
|
||||
let ciphertext = "";
|
||||
for (const p of plaintext) {
|
||||
const pp = findPos(key, p);
|
||||
const mix = findIx(findAtPos(key, mp));
|
||||
let cp = addPos(pp, mix);
|
||||
const c = findAtPos(key, cp);
|
||||
ciphertext += c;
|
||||
key = rotateRight(key, pp[0], 1);
|
||||
cp = findPos(key, c);
|
||||
key = rotateDown(key, cp[1], 1);
|
||||
mp = addPos(mp, findIx(c));
|
||||
}
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts the ciphertext string.
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} ciphertext
|
||||
* @returns {string}
|
||||
*/
|
||||
function decrypt(key, ciphertext) {
|
||||
checkKey(key);
|
||||
let mp = [0, 0];
|
||||
let plaintext = "";
|
||||
for (const c of ciphertext) {
|
||||
let cp = findPos(key, c);
|
||||
const mix = findIx(findAtPos(key, mp));
|
||||
const pp = subPos(cp, mix);
|
||||
const p = findAtPos(key, pp);
|
||||
plaintext += p;
|
||||
key = rotateRight(key, pp[0], 1);
|
||||
cp = findPos(key, c);
|
||||
key = rotateDown(key, cp[1], 1);
|
||||
mp = addPos(mp, findIx(c));
|
||||
}
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds padding to the input.
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} plaintext
|
||||
* @param {string} signature
|
||||
* @param {number} paddingSize
|
||||
* @returns {string}
|
||||
*/
|
||||
export function encryptPad(key, plaintext, signature, paddingSize) {
|
||||
initTiles();
|
||||
checkKey(key);
|
||||
let padding = "";
|
||||
for (let i = 0; i < paddingSize; i++) {
|
||||
padding += letters.charAt(Math.floor(Math.random() * letters.length));
|
||||
}
|
||||
return encrypt(key, padding+plaintext+"---"+signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes padding from the ouput.
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} ciphertext
|
||||
* @param {number} paddingSize
|
||||
* @returns {string}
|
||||
*/
|
||||
export function decryptPad(key, ciphertext, paddingSize) {
|
||||
initTiles();
|
||||
checkKey(key);
|
||||
return decrypt(key, ciphertext).slice(paddingSize);
|
||||
}
|
||||
21
src/core/lib/LZString.mjs
Normal file
21
src/core/lib/LZString.mjs
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* lz-string exports.
|
||||
*
|
||||
* @author crespyl [peter@crespyl.net]
|
||||
* @copyright Peter Jacobs 2021
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import LZString from "lz-string";
|
||||
|
||||
export const COMPRESSION_OUTPUT_FORMATS = ["default", "UTF16", "Base64"];
|
||||
export const COMPRESSION_FUNCTIONS = {
|
||||
"default": LZString.compress,
|
||||
"UTF16": LZString.compressToUTF16,
|
||||
"Base64": LZString.compressToBase64,
|
||||
};
|
||||
export const DECOMPRESSION_FUNCTIONS = {
|
||||
"default": LZString.decompress,
|
||||
"UTF16": LZString.decompressFromUTF16,
|
||||
"Base64": LZString.decompressFromBase64,
|
||||
};
|
||||
@@ -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";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Utils from "../Utils.mjs";
|
||||
import protobuf from "protobufjs";
|
||||
|
||||
/**
|
||||
* Protobuf lib. Contains functions to decode protobuf serialised
|
||||
@@ -32,9 +33,10 @@ class Protobuf {
|
||||
this.MSB = 0x80;
|
||||
this.VALUE = 0x7f;
|
||||
|
||||
// Declare offset and length
|
||||
// Declare offset, length, and field type object
|
||||
this.offset = 0;
|
||||
this.LENGTH = data.length;
|
||||
this.fieldTypes = {};
|
||||
}
|
||||
|
||||
// Public Functions
|
||||
@@ -76,15 +78,281 @@ class Protobuf {
|
||||
return pb._varInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode input JSON according to the given schema
|
||||
*
|
||||
* @param {Object} input
|
||||
* @param {Object []} args
|
||||
* @returns {Object}
|
||||
*/
|
||||
static encode(input, args) {
|
||||
this.updateProtoRoot(args[0]);
|
||||
if (!this.mainMessageName) {
|
||||
throw new Error("Schema Error: Schema not defined");
|
||||
}
|
||||
const message = this.parsedProto.root.nested[this.mainMessageName];
|
||||
|
||||
// Convert input into instance of message, and verify instance
|
||||
input = message.fromObject(input);
|
||||
const error = message.verify(input);
|
||||
if (error) {
|
||||
throw new Error("Input Error: " + error);
|
||||
}
|
||||
// Encode input
|
||||
const output = message.encode(input).finish();
|
||||
return new Uint8Array(output).buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse Protobuf data
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @returns {Object}
|
||||
*/
|
||||
static decode(input) {
|
||||
static decode(input, args) {
|
||||
this.updateProtoRoot(args[0]);
|
||||
this.showUnknownFields = args[1];
|
||||
this.showTypes = args[2];
|
||||
return this.mergeDecodes(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the parsedProto, throw parsing errors
|
||||
*
|
||||
* @param {string} protoText
|
||||
*/
|
||||
static updateProtoRoot(protoText) {
|
||||
try {
|
||||
this.parsedProto = protobuf.parse(protoText);
|
||||
if (this.parsedProto.package) {
|
||||
this.parsedProto.root = this.parsedProto.root.nested[this.parsedProto.package];
|
||||
}
|
||||
this.updateMainMessageName();
|
||||
} catch (error) {
|
||||
throw new Error("Schema " + error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set mainMessageName to the first instance of a message defined in the schema that is not a submessage
|
||||
*
|
||||
*/
|
||||
static updateMainMessageName() {
|
||||
const messageNames = [];
|
||||
const fieldTypes = [];
|
||||
this.parsedProto.root.nestedArray.forEach(block => {
|
||||
if (block instanceof protobuf.Type) {
|
||||
messageNames.push(block.name);
|
||||
this.parsedProto.root.nested[block.name].fieldsArray.forEach(field => {
|
||||
fieldTypes.push(field.type);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (messageNames.length === 0) {
|
||||
this.mainMessageName = null;
|
||||
} else {
|
||||
// for (const name of messageNames) {
|
||||
// if (!fieldTypes.includes(name)) {
|
||||
// this.mainMessageName = name;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
this.mainMessageName = messageNames[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode input using Protobufjs package and raw methods, compare, and merge results
|
||||
*
|
||||
* @param {byteArray} input
|
||||
* @returns {Object}
|
||||
*/
|
||||
static mergeDecodes(input) {
|
||||
const pb = new Protobuf(input);
|
||||
return pb._parse();
|
||||
let rawDecode = pb._parse();
|
||||
let message;
|
||||
|
||||
if (this.showTypes) {
|
||||
rawDecode = this.showRawTypes(rawDecode, pb.fieldTypes);
|
||||
this.parsedProto.root = this.appendTypesToFieldNames(this.parsedProto.root);
|
||||
}
|
||||
|
||||
try {
|
||||
message = this.parsedProto.root.nested[this.mainMessageName];
|
||||
const packageDecode = message.toObject(message.decode(input), {
|
||||
bytes: String,
|
||||
longs: Number,
|
||||
enums: String,
|
||||
defaults: true
|
||||
});
|
||||
const output = {};
|
||||
|
||||
if (this.showUnknownFields) {
|
||||
output[message.name] = packageDecode;
|
||||
output["Unknown Fields"] = this.compareFields(rawDecode, message);
|
||||
return output;
|
||||
} else {
|
||||
return packageDecode;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
if (message) {
|
||||
throw new Error("Input " + error);
|
||||
} else {
|
||||
return rawDecode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace fieldnames with fieldname and type
|
||||
*
|
||||
* @param {Object} schemaRoot
|
||||
* @returns {Object}
|
||||
*/
|
||||
static appendTypesToFieldNames(schemaRoot) {
|
||||
for (const block of schemaRoot.nestedArray) {
|
||||
if (block instanceof protobuf.Type) {
|
||||
for (const [fieldName, fieldData] of Object.entries(block.fields)) {
|
||||
schemaRoot.nested[block.name].remove(block.fields[fieldName]);
|
||||
schemaRoot.nested[block.name].add(new protobuf.Field(`${fieldName} (${fieldData.type})`, fieldData.id, fieldData.type, fieldData.rule));
|
||||
}
|
||||
}
|
||||
}
|
||||
return schemaRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add field type to field name for fields in the raw decoded output
|
||||
*
|
||||
* @param {Object} rawDecode
|
||||
* @param {Object} fieldTypes
|
||||
* @returns {Object}
|
||||
*/
|
||||
static showRawTypes(rawDecode, fieldTypes) {
|
||||
for (const [fieldNum, value] of Object.entries(rawDecode)) {
|
||||
const fieldType = fieldTypes[fieldNum];
|
||||
let outputFieldValue;
|
||||
let outputFieldType;
|
||||
|
||||
// Submessages
|
||||
if (isNaN(fieldType)) {
|
||||
outputFieldType = 2;
|
||||
|
||||
// Repeated submessages
|
||||
if (Array.isArray(value)) {
|
||||
const fieldInstances = [];
|
||||
for (const instance of Object.keys(value)) {
|
||||
if (typeof(value[instance]) !== "string") {
|
||||
fieldInstances.push(this.showRawTypes(value[instance], fieldType));
|
||||
} else {
|
||||
fieldInstances.push(value[instance]);
|
||||
}
|
||||
}
|
||||
outputFieldValue = fieldInstances;
|
||||
|
||||
// Single submessage
|
||||
} else {
|
||||
outputFieldValue = this.showRawTypes(value, fieldType);
|
||||
}
|
||||
|
||||
// Non-submessage field
|
||||
} else {
|
||||
outputFieldType = fieldType;
|
||||
outputFieldValue = value;
|
||||
}
|
||||
|
||||
// Substitute fieldNum with field number and type
|
||||
rawDecode[`field #${fieldNum}: ${this.getTypeInfo(outputFieldType)}`] = outputFieldValue;
|
||||
delete rawDecode[fieldNum];
|
||||
}
|
||||
return rawDecode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare raw decode to package decode and return discrepancies
|
||||
*
|
||||
* @param rawDecodedMessage
|
||||
* @param schemaMessage
|
||||
* @returns {Object}
|
||||
*/
|
||||
static compareFields(rawDecodedMessage, schemaMessage) {
|
||||
// Define message data using raw decode output and schema
|
||||
const schemaFieldProperties = {};
|
||||
const schemaFieldNames = Object.keys(schemaMessage.fields);
|
||||
schemaFieldNames.forEach(field => schemaFieldProperties[schemaMessage.fields[field].id] = field);
|
||||
|
||||
// Loop over each field present in the raw decode output
|
||||
for (const fieldName in rawDecodedMessage) {
|
||||
let fieldId;
|
||||
if (isNaN(fieldName)) {
|
||||
fieldId = fieldName.match(/^field #(\d+)/)[1];
|
||||
} else {
|
||||
fieldId = fieldName;
|
||||
}
|
||||
|
||||
// Check if this field is defined in the schema
|
||||
if (fieldId in schemaFieldProperties) {
|
||||
const schemaFieldName = schemaFieldProperties[fieldId];
|
||||
|
||||
// Extract the current field data from the raw decode and schema
|
||||
const rawFieldData = rawDecodedMessage[fieldName];
|
||||
const schemaField = schemaMessage.fields[schemaFieldName];
|
||||
|
||||
// Check for repeated fields
|
||||
if (Array.isArray(rawFieldData) && !schemaField.repeated) {
|
||||
rawDecodedMessage[`(${schemaMessage.name}) ${schemaFieldName} is a repeated field`] = rawFieldData;
|
||||
}
|
||||
|
||||
// Check for submessage fields
|
||||
if (schemaField.resolvedType instanceof protobuf.Type) {
|
||||
const subMessageType = schemaMessage.fields[schemaFieldName].type;
|
||||
const schemaSubMessage = this.parsedProto.root.nested[subMessageType];
|
||||
const rawSubMessages = rawDecodedMessage[fieldName];
|
||||
let rawDecodedSubMessage = {};
|
||||
|
||||
// Squash multiple submessage instances into one submessage
|
||||
if (Array.isArray(rawSubMessages)) {
|
||||
rawSubMessages.forEach(subMessageInstance => {
|
||||
const instanceFields = Object.entries(subMessageInstance);
|
||||
instanceFields.forEach(subField => {
|
||||
rawDecodedSubMessage[subField[0]] = subField[1];
|
||||
});
|
||||
});
|
||||
} else {
|
||||
rawDecodedSubMessage = rawSubMessages;
|
||||
}
|
||||
|
||||
// Treat submessage as own message and compare its fields
|
||||
rawDecodedSubMessage = Protobuf.compareFields(rawDecodedSubMessage, schemaSubMessage);
|
||||
if (Object.entries(rawDecodedSubMessage).length !== 0) {
|
||||
rawDecodedMessage[`${schemaFieldName} (${subMessageType}) has missing fields`] = rawDecodedSubMessage;
|
||||
}
|
||||
}
|
||||
delete rawDecodedMessage[fieldName];
|
||||
}
|
||||
}
|
||||
return rawDecodedMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns wiretype information for input wiretype number
|
||||
*
|
||||
* @param {number} wireType
|
||||
* @returns {string}
|
||||
*/
|
||||
static getTypeInfo(wireType) {
|
||||
switch (wireType) {
|
||||
case 0:
|
||||
return "VarInt (e.g. int32, bool)";
|
||||
case 1:
|
||||
return "64-Bit (e.g. fixed64, double)";
|
||||
case 2:
|
||||
return "L-delim (e.g. string, message)";
|
||||
case 5:
|
||||
return "32-Bit (e.g. fixed32, float)";
|
||||
}
|
||||
}
|
||||
|
||||
// Private Class Functions
|
||||
@@ -143,6 +411,11 @@ class Protobuf {
|
||||
const header = this._fieldHeader();
|
||||
const type = header.type;
|
||||
const key = header.key;
|
||||
|
||||
if (typeof(this.fieldTypes[key]) !== "object") {
|
||||
this.fieldTypes[key] = type;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
// varint
|
||||
case 0:
|
||||
@@ -152,7 +425,7 @@ class Protobuf {
|
||||
return { "key": key, "value": this._uint64() };
|
||||
// length delimited
|
||||
case 2:
|
||||
return { "key": key, "value": this._lenDelim() };
|
||||
return { "key": key, "value": this._lenDelim(key) };
|
||||
// fixed 32
|
||||
case 5:
|
||||
return { "key": key, "value": this._uint32() };
|
||||
@@ -237,10 +510,10 @@ class Protobuf {
|
||||
* @returns {number}
|
||||
*/
|
||||
_uint64() {
|
||||
// Read off a Uint64
|
||||
let num = this.data[this.offset++] * 0x1000000 + (this.data[this.offset++] << 16) + (this.data[this.offset++] << 8) + this.data[this.offset++];
|
||||
num = num * 0x100000000 + this.data[this.offset++] * 0x1000000 + (this.data[this.offset++] << 16) + (this.data[this.offset++] << 8) + this.data[this.offset++];
|
||||
return num;
|
||||
// Read off a Uint64 with little-endian
|
||||
const lowerHalf = this.data[this.offset++] + (this.data[this.offset++] * 0x100) + (this.data[this.offset++] * 0x10000) + this.data[this.offset++] * 0x1000000;
|
||||
const upperHalf = this.data[this.offset++] + (this.data[this.offset++] * 0x100) + (this.data[this.offset++] * 0x10000) + this.data[this.offset++] * 0x1000000;
|
||||
return upperHalf * 0x100000000 + lowerHalf;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -249,7 +522,7 @@ class Protobuf {
|
||||
* @private
|
||||
* @returns {Object|string}
|
||||
*/
|
||||
_lenDelim() {
|
||||
_lenDelim(fieldNum) {
|
||||
// Read off the field length
|
||||
const length = this._varInt();
|
||||
const fieldBytes = this.data.slice(this.offset, this.offset + length);
|
||||
@@ -258,6 +531,10 @@ class Protobuf {
|
||||
// Attempt to parse as a new Protobuf Object
|
||||
const pbObject = new Protobuf(fieldBytes);
|
||||
field = pbObject._parse();
|
||||
|
||||
// Set field types object
|
||||
this.fieldTypes[fieldNum] = {...this.fieldTypes[fieldNum], ...pbObject.fieldTypes};
|
||||
|
||||
} catch (err) {
|
||||
// Otherwise treat as bytes
|
||||
field = Utils.byteArrayToChars(fieldBytes);
|
||||
@@ -276,7 +553,7 @@ class Protobuf {
|
||||
_uint32() {
|
||||
// Use a dataview to read off the integer
|
||||
const dataview = new DataView(new Uint8Array(this.data.slice(this.offset, this.offset + 4)).buffer);
|
||||
const value = dataview.getUint32(0);
|
||||
const value = dataview.getUint32(0, true);
|
||||
this.offset += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
47
src/core/lib/Protocol.mjs
Normal file
47
src/core/lib/Protocol.mjs
Normal 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();
|
||||
}
|
||||
@@ -9,35 +9,25 @@
|
||||
import { toHex, fromHex } from "./Hex.mjs";
|
||||
|
||||
/**
|
||||
* Formats Distinguished Name (DN) strings.
|
||||
* Formats Distinguished Name (DN) objects to strings.
|
||||
*
|
||||
* @param {string} dnStr
|
||||
* @param {Object} dnObj
|
||||
* @param {number} indent
|
||||
* @returns {string}
|
||||
*/
|
||||
export function formatDnStr(dnStr, indent) {
|
||||
const fields = dnStr.substr(1).replace(/([^\\])\//g, "$1$1/").split(/[^\\]\//);
|
||||
let output = "",
|
||||
maxKeyLen = 0,
|
||||
key,
|
||||
value,
|
||||
i,
|
||||
str;
|
||||
export function formatDnObj(dnObj, indent) {
|
||||
let output = "";
|
||||
|
||||
for (i = 0; i < fields.length; i++) {
|
||||
if (!fields[i].length) continue;
|
||||
const maxKeyLen = dnObj.array.reduce((max, item) => {
|
||||
return item[0].type.length > max ? item[0].type.length : max;
|
||||
}, 0);
|
||||
|
||||
key = fields[i].split("=")[0];
|
||||
for (let i = 0; i < dnObj.array.length; i++) {
|
||||
if (!dnObj.array[i].length) continue;
|
||||
|
||||
maxKeyLen = key.length > maxKeyLen ? key.length : maxKeyLen;
|
||||
}
|
||||
|
||||
for (i = 0; i < fields.length; i++) {
|
||||
if (!fields[i].length) continue;
|
||||
|
||||
key = fields[i].split("=")[0];
|
||||
value = fields[i].split("=")[1];
|
||||
str = key.padEnd(maxKeyLen, " ") + " = " + value + "\n";
|
||||
const key = dnObj.array[i][0].type;
|
||||
const value = dnObj.array[i][0].value;
|
||||
const str = `${key.padEnd(maxKeyLen, " ")} = ${value}\n`;
|
||||
|
||||
output += str.padStart(indent + str.length, " ");
|
||||
}
|
||||
|
||||
502
src/core/lib/SIGABA.mjs
Normal file
502
src/core/lib/SIGABA.mjs
Normal 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
331
src/core/lib/SM4.mjs
Normal 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);
|
||||
}
|
||||
|
||||
117
src/core/lib/Sort.mjs
Normal file
117
src/core/lib/Sort.mjs
Normal file
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparison operation for sorting by length
|
||||
*
|
||||
* @param {string} a
|
||||
* @param {string} b
|
||||
* @returns {number}
|
||||
*/
|
||||
export function lengthSort(a, b) {
|
||||
return a.length - b.length;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -46,12 +48,14 @@ export default class Stream {
|
||||
* Interpret the following bytes as a string, stopping at the next null byte or
|
||||
* the supplied limit.
|
||||
*
|
||||
* @param {number} numBytes
|
||||
* @param {number} [numBytes=-1]
|
||||
* @returns {string}
|
||||
*/
|
||||
readString(numBytes) {
|
||||
readString(numBytes=-1) {
|
||||
if (this.position > this.length) return undefined;
|
||||
|
||||
if (numBytes === -1) numBytes = this.length - this.position;
|
||||
|
||||
let result = "";
|
||||
for (let i = this.position; i < this.position + numBytes; i++) {
|
||||
const currentByte = this.bytes[i];
|
||||
@@ -91,34 +95,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 +143,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
149
src/core/operations/CMAC.mjs
Normal file
149
src/core/operations/CMAC.mjs
Normal file
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* @author mikecat
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import forge from "node-forge";
|
||||
import { toHexFast } from "../lib/Hex.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* CMAC operation
|
||||
*/
|
||||
class CMAC extends Operation {
|
||||
|
||||
/**
|
||||
* CMAC constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "CMAC";
|
||||
this.module = "Crypto";
|
||||
this.description = "CMAC is a block-cipher based message authentication code algorithm.<br><br>RFC4493 defines AES-CMAC that uses AES encryption with a 128-bit key.<br>NIST SP 800-38B suggests usages of AES with other key lengths and Triple DES.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/CMAC";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Key",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "Encryption algorithm",
|
||||
"type": "option",
|
||||
"value": ["AES", "Triple DES"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const key = Utils.convertToByteString(args[0].string, args[0].option);
|
||||
const algo = args[1];
|
||||
|
||||
const info = (function() {
|
||||
switch (algo) {
|
||||
case "AES":
|
||||
if (key.length !== 16 && key.length !== 24 && key.length !== 32) {
|
||||
throw new OperationError("The key for AES must be either 16, 24, or 32 bytes (currently " + key.length + " bytes)");
|
||||
}
|
||||
return {
|
||||
"algorithm": "AES-ECB",
|
||||
"key": key,
|
||||
"blockSize": 16,
|
||||
"Rb": new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x87]),
|
||||
};
|
||||
case "Triple DES":
|
||||
if (key.length !== 16 && key.length !== 24) {
|
||||
throw new OperationError("The key for Triple DES must be 16 or 24 bytes (currently " + key.length + " bytes)");
|
||||
}
|
||||
return {
|
||||
"algorithm": "3DES-ECB",
|
||||
"key": key.length === 16 ? key + key.substring(0, 8) : key,
|
||||
"blockSize": 8,
|
||||
"Rb": new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0x1b]),
|
||||
};
|
||||
default:
|
||||
throw new OperationError("Undefined encryption algorithm");
|
||||
}
|
||||
})();
|
||||
|
||||
const xor = function(a, b, out) {
|
||||
if (!out) out = new Uint8Array(a.length);
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
out[i] = a[i] ^ b[i];
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
const leftShift1 = function(a) {
|
||||
const out = new Uint8Array(a.length);
|
||||
let carry = 0;
|
||||
for (let i = a.length - 1; i >= 0; i--) {
|
||||
out[i] = (a[i] << 1) | carry;
|
||||
carry = a[i] >> 7;
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
const cipher = forge.cipher.createCipher(info.algorithm, info.key);
|
||||
const encrypt = function(a, out) {
|
||||
if (!out) out = new Uint8Array(a.length);
|
||||
cipher.start();
|
||||
cipher.update(forge.util.createBuffer(a));
|
||||
cipher.finish();
|
||||
const cipherText = cipher.output.getBytes();
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
out[i] = cipherText.charCodeAt(i);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
const L = encrypt(new Uint8Array(info.blockSize));
|
||||
const K1 = leftShift1(L);
|
||||
if (L[0] & 0x80) xor(K1, info.Rb, K1);
|
||||
const K2 = leftShift1(K1);
|
||||
if (K1[0] & 0x80) xor(K2, info.Rb, K2);
|
||||
|
||||
const n = Math.ceil(input.byteLength / info.blockSize);
|
||||
const lastBlock = (function() {
|
||||
if (n === 0) {
|
||||
const data = new Uint8Array(K2);
|
||||
data[0] ^= 0x80;
|
||||
return data;
|
||||
}
|
||||
const inputLast = new Uint8Array(input, info.blockSize * (n - 1));
|
||||
if (inputLast.length === info.blockSize) {
|
||||
return xor(inputLast, K1, inputLast);
|
||||
} else {
|
||||
const data = new Uint8Array(info.blockSize);
|
||||
data.set(inputLast, 0);
|
||||
data[inputLast.length] = 0x80;
|
||||
return xor(data, K2, data);
|
||||
}
|
||||
})();
|
||||
|
||||
const X = new Uint8Array(info.blockSize);
|
||||
const Y = new Uint8Array(info.blockSize);
|
||||
for (let i = 0; i < n - 1; i++) {
|
||||
xor(X, new Uint8Array(input, info.blockSize * i, info.blockSize), Y);
|
||||
encrypt(Y, X);
|
||||
}
|
||||
xor(lastBlock, X, Y);
|
||||
const T = encrypt(Y);
|
||||
return toHexFast(T);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CMAC;
|
||||
61
src/core/operations/CaesarBoxCipher.mjs
Normal file
61
src/core/operations/CaesarBoxCipher.mjs
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* @author n1073645 [n1073645@gmail.com]
|
||||
* @copyright Crown Copyright 2020
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
|
||||
/**
|
||||
* Caesar Box Cipher operation
|
||||
*/
|
||||
class CaesarBoxCipher extends Operation {
|
||||
|
||||
/**
|
||||
* CaesarBoxCipher constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Caesar Box Cipher";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Caesar Box is a transposition cipher used in the Roman Empire, in which letters of the message are written in rows in a square (or a rectangle) and then, read by column.";
|
||||
this.infoURL = "https://www.dcode.fr/caesar-box-cipher";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Box Height",
|
||||
type: "number",
|
||||
value: 1
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const tableHeight = args[0];
|
||||
const tableWidth = Math.ceil(input.length / tableHeight);
|
||||
while (input.indexOf(" ") !== -1)
|
||||
input = input.replace(" ", "");
|
||||
for (let i = 0; i < (tableHeight * tableWidth) - input.length; i++) {
|
||||
input += "\x00";
|
||||
}
|
||||
let result = "";
|
||||
for (let i = 0; i < tableHeight; i++) {
|
||||
for (let j = i; j < input.length; j += tableHeight) {
|
||||
if (input.charAt(j) !== "\x00") {
|
||||
result += input.charAt(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CaesarBoxCipher;
|
||||
63
src/core/operations/CetaceanCipherDecode.mjs
Normal file
63
src/core/operations/CetaceanCipherDecode.mjs
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* @author dolphinOnKeys [robin@weird.io]
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
|
||||
/**
|
||||
* Cetacean Cipher Decode operation
|
||||
*/
|
||||
class CetaceanCipherDecode extends Operation {
|
||||
|
||||
/**
|
||||
* CetaceanCipherDecode constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Cetacean Cipher Decode";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Decode Cetacean Cipher input. <br/><br/>e.g. <code>EEEEEEEEEeeEeEEEEEEEEEEEEeeEeEEe</code> becomes <code>hi</code>";
|
||||
this.infoURL = "https://hitchhikers.fandom.com/wiki/Dolphins";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
|
||||
this.checks = [
|
||||
{
|
||||
pattern: "^(?:[eE]{16,})(?: [eE]{16,})*$",
|
||||
flags: "",
|
||||
args: []
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const binaryArray = [];
|
||||
for (const char of input) {
|
||||
if (char === " ") {
|
||||
binaryArray.push(...[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]);
|
||||
} else {
|
||||
binaryArray.push(char === "e" ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
const byteArray = [];
|
||||
|
||||
for (let i = 0; i < binaryArray.length; i += 16) {
|
||||
byteArray.push(binaryArray.slice(i, i + 16).join(""));
|
||||
}
|
||||
|
||||
return byteArray.map(byte =>
|
||||
String.fromCharCode(parseInt(byte, 2))
|
||||
).join("");
|
||||
}
|
||||
}
|
||||
|
||||
export default CetaceanCipherDecode;
|
||||
51
src/core/operations/CetaceanCipherEncode.mjs
Normal file
51
src/core/operations/CetaceanCipherEncode.mjs
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @author dolphinOnKeys [robin@weird.io]
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import {toBinary} from "../lib/Binary.mjs";
|
||||
|
||||
/**
|
||||
* Cetacean Cipher Encode operation
|
||||
*/
|
||||
class CetaceanCipherEncode extends Operation {
|
||||
|
||||
/**
|
||||
* CetaceanCipherEncode constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Cetacean Cipher Encode";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Converts any input into Cetacean Cipher. <br/><br/>e.g. <code>hi</code> becomes <code>EEEEEEEEEeeEeEEEEEEEEEEEEeeEeEEe</code>";
|
||||
this.infoURL = "https://hitchhikers.fandom.com/wiki/Dolphins";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const result = [];
|
||||
const charArray = input.split("");
|
||||
|
||||
charArray.map(character => {
|
||||
if (character === " ") {
|
||||
result.push(character);
|
||||
} else {
|
||||
const binaryArray = toBinary(character.charCodeAt(0), "None", 16).split("");
|
||||
result.push(binaryArray.map(str => str === "1" ? "e" : "E").join(""));
|
||||
}
|
||||
});
|
||||
|
||||
return result.join("");
|
||||
}
|
||||
}
|
||||
|
||||
export default CetaceanCipherEncode;
|
||||
234
src/core/operations/ChaCha.mjs
Normal file
234
src/core/operations/ChaCha.mjs
Normal file
@@ -0,0 +1,234 @@
|
||||
/**
|
||||
* @author joostrijneveld [joost@joostrijneveld.nl]
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import { toHex } from "../lib/Hex.mjs";
|
||||
|
||||
/**
|
||||
* Computes the ChaCha block function
|
||||
*
|
||||
* @param {byteArray} key
|
||||
* @param {byteArray} nonce
|
||||
* @param {byteArray} counter
|
||||
* @param {integer} rounds
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
function chacha(key, nonce, counter, rounds) {
|
||||
const tau = "expand 16-byte k";
|
||||
const sigma = "expand 32-byte k";
|
||||
|
||||
let state, c;
|
||||
if (key.length === 16) {
|
||||
c = Utils.strToByteArray(tau);
|
||||
state = c.concat(key).concat(key);
|
||||
} else {
|
||||
c = Utils.strToByteArray(sigma);
|
||||
state = c.concat(key);
|
||||
}
|
||||
state = state.concat(counter).concat(nonce);
|
||||
|
||||
const x = Array();
|
||||
for (let i = 0; i < 64; i += 4) {
|
||||
x.push(Utils.byteArrayToInt(state.slice(i, i + 4), "little"));
|
||||
}
|
||||
const a = [...x];
|
||||
|
||||
/**
|
||||
* Macro to compute a 32-bit rotate-left operation
|
||||
*
|
||||
* @param {integer} x
|
||||
* @param {integer} n
|
||||
* @returns {integer}
|
||||
*/
|
||||
function ROL32(x, n) {
|
||||
return ((x << n) & 0xFFFFFFFF) | (x >>> (32 - n));
|
||||
}
|
||||
|
||||
/**
|
||||
* Macro to compute a single ChaCha quarterround operation
|
||||
*
|
||||
* @param {integer} x
|
||||
* @param {integer} a
|
||||
* @param {integer} b
|
||||
* @param {integer} c
|
||||
* @param {integer} d
|
||||
* @returns {integer}
|
||||
*/
|
||||
function quarterround(x, a, b, c, d) {
|
||||
x[a] = ((x[a] + x[b]) & 0xFFFFFFFF); x[d] = ROL32(x[d] ^ x[a], 16);
|
||||
x[c] = ((x[c] + x[d]) & 0xFFFFFFFF); x[b] = ROL32(x[b] ^ x[c], 12);
|
||||
x[a] = ((x[a] + x[b]) & 0xFFFFFFFF); x[d] = ROL32(x[d] ^ x[a], 8);
|
||||
x[c] = ((x[c] + x[d]) & 0xFFFFFFFF); x[b] = ROL32(x[b] ^ x[c], 7);
|
||||
}
|
||||
|
||||
for (let i = 0; i < rounds / 2; i++) {
|
||||
quarterround(x, 0, 4, 8, 12);
|
||||
quarterround(x, 1, 5, 9, 13);
|
||||
quarterround(x, 2, 6, 10, 14);
|
||||
quarterround(x, 3, 7, 11, 15);
|
||||
quarterround(x, 0, 5, 10, 15);
|
||||
quarterround(x, 1, 6, 11, 12);
|
||||
quarterround(x, 2, 7, 8, 13);
|
||||
quarterround(x, 3, 4, 9, 14);
|
||||
}
|
||||
|
||||
for (let i = 0; i < 16; i++) {
|
||||
x[i] = (x[i] + a[i]) & 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
let output = Array();
|
||||
for (let i = 0; i < 16; i++) {
|
||||
output = output.concat(Utils.intToByteArray(x[i], 4, "little"));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* ChaCha operation
|
||||
*/
|
||||
class ChaCha extends Operation {
|
||||
|
||||
/**
|
||||
* ChaCha constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "ChaCha";
|
||||
this.module = "Default";
|
||||
this.description = "ChaCha is a stream cipher designed by Daniel J. Bernstein. It is a variant of the Salsa stream cipher. Several parameterizations exist; 'ChaCha' may refer to the original construction, or to the variant as described in RFC-8439. ChaCha is often used with Poly1305, in the ChaCha20-Poly1305 AEAD construction.<br><br><b>Key:</b> ChaCha uses a key of 16 or 32 bytes (128 or 256 bits).<br><br><b>Nonce:</b> ChaCha uses a nonce of 8 or 12 bytes (64 or 96 bits).<br><br><b>Counter:</b> ChaCha uses a counter of 4 or 8 bytes (32 or 64 bits); together, the nonce and counter must add up to 16 bytes. The counter starts at zero at the start of the keystream, and is incremented at every 64 bytes.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Salsa20#ChaCha_variant";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Key",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "Nonce",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64", "Integer"]
|
||||
},
|
||||
{
|
||||
"name": "Counter",
|
||||
"type": "number",
|
||||
"value": 0,
|
||||
"min": 0
|
||||
},
|
||||
{
|
||||
"name": "Rounds",
|
||||
"type": "option",
|
||||
"value": ["20", "12", "8"]
|
||||
},
|
||||
{
|
||||
"name": "Input",
|
||||
"type": "option",
|
||||
"value": ["Hex", "Raw"]
|
||||
},
|
||||
{
|
||||
"name": "Output",
|
||||
"type": "option",
|
||||
"value": ["Raw", "Hex"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const key = Utils.convertToByteArray(args[0].string, args[0].option),
|
||||
nonceType = args[1].option,
|
||||
rounds = parseInt(args[3], 10),
|
||||
inputType = args[4],
|
||||
outputType = args[5];
|
||||
|
||||
if (key.length !== 16 && key.length !== 32) {
|
||||
throw new OperationError(`Invalid key length: ${key.length} bytes.
|
||||
|
||||
ChaCha uses a key of 16 or 32 bytes (128 or 256 bits).`);
|
||||
}
|
||||
|
||||
let counter, nonce, counterLength;
|
||||
if (nonceType === "Integer") {
|
||||
nonce = Utils.intToByteArray(parseInt(args[1].string, 10), 12, "little");
|
||||
counterLength = 4;
|
||||
} else {
|
||||
nonce = Utils.convertToByteArray(args[1].string, args[1].option);
|
||||
if (!(nonce.length === 12 || nonce.length === 8)) {
|
||||
throw new OperationError(`Invalid nonce length: ${nonce.length} bytes.
|
||||
|
||||
ChaCha uses a nonce of 8 or 12 bytes (64 or 96 bits).`);
|
||||
}
|
||||
counterLength = 16 - nonce.length;
|
||||
}
|
||||
counter = Utils.intToByteArray(args[2], counterLength, "little");
|
||||
|
||||
const output = [];
|
||||
input = Utils.convertToByteArray(input, inputType);
|
||||
|
||||
let counterAsInt = Utils.byteArrayToInt(counter, "little");
|
||||
for (let i = 0; i < input.length; i += 64) {
|
||||
counter = Utils.intToByteArray(counterAsInt, counterLength, "little");
|
||||
const stream = chacha(key, nonce, counter, rounds);
|
||||
for (let j = 0; j < 64 && i + j < input.length; j++) {
|
||||
output.push(input[i + j] ^ stream[j]);
|
||||
}
|
||||
counterAsInt++;
|
||||
}
|
||||
|
||||
if (outputType === "Hex") {
|
||||
return toHex(output);
|
||||
} else {
|
||||
return Utils.arrayBufferToStr(output);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight ChaCha
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlight(pos, args) {
|
||||
const inputType = args[4],
|
||||
outputType = args[5];
|
||||
if (inputType === "Raw" && outputType === "Raw") {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight ChaCha in reverse
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightReverse(pos, args) {
|
||||
const inputType = args[4],
|
||||
outputType = args[5];
|
||||
if (inputType === "Raw" && outputType === "Raw") {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ChaCha;
|
||||
@@ -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
|
||||
|
||||
@@ -64,6 +64,7 @@ class ConditionalJump extends Operation {
|
||||
jmpIndex = getLabelIndex(label, state);
|
||||
|
||||
if (state.numJumps >= maxJumps || jmpIndex === -1) {
|
||||
state.numJumps = 0;
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -73,6 +74,8 @@ class ConditionalJump extends Operation {
|
||||
if (!invert && strMatch || invert && !strMatch) {
|
||||
state.progress = jmpIndex;
|
||||
state.numJumps++;
|
||||
} else {
|
||||
state.numJumps = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -51,10 +51,27 @@ class DNSOverHTTPS extends Operation {
|
||||
value: [
|
||||
"A",
|
||||
"AAAA",
|
||||
"TXT",
|
||||
"MX",
|
||||
"ANAME",
|
||||
"CERT",
|
||||
"CNAME",
|
||||
"DNSKEY",
|
||||
"NS"
|
||||
"HTTPS",
|
||||
"IPSECKEY",
|
||||
"LOC",
|
||||
"MX",
|
||||
"NS",
|
||||
"OPENPGPKEY",
|
||||
"PTR",
|
||||
"RRSIG",
|
||||
"SIG",
|
||||
"SOA",
|
||||
"SPF",
|
||||
"SRV",
|
||||
"SSHFP",
|
||||
"TA",
|
||||
"TXT",
|
||||
"URI",
|
||||
"ANY"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
913
src/core/operations/ELFInfo.mjs
Normal file
913
src/core/operations/ELFInfo.mjs
Normal file
@@ -0,0 +1,913 @@
|
||||
/**
|
||||
* @author n1073645 [n1073645@gmail.com]
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Stream from "../lib/Stream.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* ELF Info operation
|
||||
*/
|
||||
class ELFInfo extends Operation {
|
||||
|
||||
/**
|
||||
* ELFInfo constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "ELF Info";
|
||||
this.module = "Default";
|
||||
this.description = "Implements readelf-like functionality. This operation will extract the ELF Header, Program Headers, Section Headers and Symbol Table for an ELF file.";
|
||||
this.infoURL = "https://www.wikipedia.org/wiki/Executable_and_Linkable_Format";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
let phoff = 0;
|
||||
let phEntries = 0;
|
||||
let shoff = 0;
|
||||
let shEntries = 0;
|
||||
let shentSize = 0;
|
||||
let entry = 0;
|
||||
let format = 0;
|
||||
let endianness = "";
|
||||
let shstrtab = 0;
|
||||
|
||||
let namesOffset = 0;
|
||||
|
||||
let symtabOffset = 0;
|
||||
let symtabSize = 0;
|
||||
let symtabEntSize = 0;
|
||||
|
||||
let strtabOffset = 0;
|
||||
const align = 30;
|
||||
|
||||
/**
|
||||
* This function reads characters until it hits a null terminator.
|
||||
*
|
||||
* @param {stream} stream
|
||||
* @param {integer} namesOffset
|
||||
* @param {integer} nameOffset
|
||||
* @returns {string}
|
||||
*/
|
||||
function readString(stream, namesOffset, nameOffset) {
|
||||
const preMove = stream.position;
|
||||
stream.moveTo(namesOffset + nameOffset);
|
||||
|
||||
const nameResult = stream.readString();
|
||||
stream.moveTo(preMove);
|
||||
return nameResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function parses and extracts relevant information from the ELF Header.
|
||||
*
|
||||
* @param {stream} stream
|
||||
* @returns {string}
|
||||
*/
|
||||
function elfHeader(stream) {
|
||||
/**
|
||||
* The ELF Header is comprised of the following structures depending on the binary's format.
|
||||
*
|
||||
* e_ident - The Magic Number 0x7F,0x45,0x4c,0x46
|
||||
* - Byte set to 1 or 2 to signify 32-bit or 64-bit format, respectively.
|
||||
* - Byte set to 1 or 2 to signify little of big endianness, respectively.
|
||||
* - Byte set to 1 for the version of ELF.
|
||||
* - Byte identifying the target OS ABI.
|
||||
* - Byte further identifying the OS ABI Version.
|
||||
* - 7 Padding Bytes.
|
||||
* e_type - 2 bytes identifying the object file type.
|
||||
* e_machine - 2 bytes identifying the instruction set architecture.
|
||||
* e_version - Byte set to 1 for the version of ELF.
|
||||
*
|
||||
* 32-bit:
|
||||
* e_entry - 4 Bytes specifying the entry point.
|
||||
* e_phoff - 4 Bytes specifying the offset of the Program Header Table.
|
||||
* e_shoff - 4 Bytes specifying the offset of the Section Header Table.
|
||||
*
|
||||
* 64-bit:
|
||||
* e_entry - 8 Bytes specifying the entry point.
|
||||
* e_phoff - 8 Bytes specifying the offset of the Program Header Table.
|
||||
* e_shoff - 8 Bytes specifying the offset of the Section Header Table.
|
||||
*
|
||||
* e_flags - 4 Bytes specifying processor specific flags.
|
||||
* e_ehsize - 2 Bytes specifying the size of the ELF Header.
|
||||
* e_phentsize - 2 Bytes specifying the size of a Program Header Table Entry.
|
||||
* e_phnum - 2 Bytes specifying the number of entries in the Program Header Table.
|
||||
* e_shentsize - 2 Bytes specifying the size of a Section Header Table Entry.
|
||||
* e_shnum - 2 Bytes specifying the number of entries in the Section Header Table.
|
||||
* e_shstrndx - 2 Bytes specifying the index of the section containing the section names in the Section Header Table.
|
||||
*/
|
||||
const ehResult = [];
|
||||
|
||||
const magic = stream.getBytes(4);
|
||||
if (magic.join("") !== [0x7f, 0x45, 0x4c, 0x46].join(""))
|
||||
throw new OperationError("Invalid ELF");
|
||||
|
||||
ehResult.push("Magic:".padEnd(align) + `${Utils.byteArrayToChars(magic)}`);
|
||||
|
||||
format = stream.readInt(1);
|
||||
ehResult.push("Format:".padEnd(align) + `${format === 1 ? "32-bit" : "64-bit"}`);
|
||||
|
||||
endianness = stream.readInt(1) === 1 ? "le" : "be";
|
||||
ehResult.push("Endianness:".padEnd(align) + `${endianness === "le" ? "Little" : "Big"}`);
|
||||
|
||||
ehResult.push("Version:".padEnd(align) + `${stream.readInt(1).toString()}`);
|
||||
|
||||
let ABI = "";
|
||||
switch (stream.readInt(1)) {
|
||||
case 0x00:
|
||||
ABI = "System V";
|
||||
break;
|
||||
case 0x01:
|
||||
ABI = "HP-UX";
|
||||
break;
|
||||
case 0x02:
|
||||
ABI = "NetBSD";
|
||||
break;
|
||||
case 0x03:
|
||||
ABI = "Linux";
|
||||
break;
|
||||
case 0x04:
|
||||
ABI = "GNU Hurd";
|
||||
break;
|
||||
case 0x06:
|
||||
ABI = "Solaris";
|
||||
break;
|
||||
case 0x07:
|
||||
ABI = "AIX";
|
||||
break;
|
||||
case 0x08:
|
||||
ABI = "IRIX";
|
||||
break;
|
||||
case 0x09:
|
||||
ABI = "FreeBSD";
|
||||
break;
|
||||
case 0x0A:
|
||||
ABI = "Tru64";
|
||||
break;
|
||||
case 0x0B:
|
||||
ABI = "Novell Modesto";
|
||||
break;
|
||||
case 0x0C:
|
||||
ABI = "OpenBSD";
|
||||
break;
|
||||
case 0x0D:
|
||||
ABI = "OpenVMS";
|
||||
break;
|
||||
case 0x0E:
|
||||
ABI = "NonStop Kernel";
|
||||
break;
|
||||
case 0x0F:
|
||||
ABI = "AROS";
|
||||
break;
|
||||
case 0x10:
|
||||
ABI = "Fenix OS";
|
||||
break;
|
||||
case 0x11:
|
||||
ABI = "CloudABI";
|
||||
break;
|
||||
case 0x12:
|
||||
ABI = "Stratus Technologies OpenVOS";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ehResult.push("ABI:".padEnd(align) + ABI);
|
||||
|
||||
// Linux Kernel does not use ABI Version.
|
||||
const abiVersion = stream.readInt(1).toString();
|
||||
if (ABI !== "Linux")
|
||||
ehResult.push("ABI Version:".padEnd(align) + abiVersion);
|
||||
|
||||
stream.moveForwardsBy(7);
|
||||
|
||||
let eType = "";
|
||||
switch (stream.readInt(2, endianness)) {
|
||||
case 0x0000:
|
||||
eType = "Unknown";
|
||||
break;
|
||||
case 0x0001:
|
||||
eType = "Relocatable File";
|
||||
break;
|
||||
case 0x0002:
|
||||
eType = "Executable File";
|
||||
break;
|
||||
case 0x0003:
|
||||
eType = "Shared Object";
|
||||
break;
|
||||
case 0x0004:
|
||||
eType = "Core File";
|
||||
break;
|
||||
case 0xFE00:
|
||||
eType = "LOOS";
|
||||
break;
|
||||
case 0xFEFF:
|
||||
eType = "HIOS";
|
||||
break;
|
||||
case 0xFF00:
|
||||
eType = "LOPROC";
|
||||
break;
|
||||
case 0xFFFF:
|
||||
eType = "HIPROC";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ehResult.push("Type:".padEnd(align) + eType);
|
||||
|
||||
let ISA = "";
|
||||
switch (stream.readInt(2, endianness)) {
|
||||
case 0x0000:
|
||||
ISA = "No specific instruction set";
|
||||
break;
|
||||
case 0x0001:
|
||||
ISA = "AT&T WE 32100";
|
||||
break;
|
||||
case 0x0002:
|
||||
ISA = "SPARC";
|
||||
break;
|
||||
case 0x0003:
|
||||
ISA = "x86";
|
||||
break;
|
||||
case 0x0004:
|
||||
ISA = "Motorola 68000 (M68k)";
|
||||
break;
|
||||
case 0x0005:
|
||||
ISA = "Motorola 88000 (M88k)";
|
||||
break;
|
||||
case 0x0006:
|
||||
ISA = "Intel MCU";
|
||||
break;
|
||||
case 0x0007:
|
||||
ISA = "Intel 80860";
|
||||
break;
|
||||
case 0x0008:
|
||||
ISA = "MIPS";
|
||||
break;
|
||||
case 0x0009:
|
||||
ISA = "IBM System/370";
|
||||
break;
|
||||
case 0x000A:
|
||||
ISA = "MIPS RS3000 Little-endian";
|
||||
break;
|
||||
case 0x000B:
|
||||
case 0x000C:
|
||||
case 0x000D:
|
||||
case 0x000E:
|
||||
case 0x0018:
|
||||
case 0x0019:
|
||||
case 0x001A:
|
||||
case 0x001B:
|
||||
case 0x001C:
|
||||
case 0x001D:
|
||||
case 0x001E:
|
||||
case 0x001F:
|
||||
case 0x0020:
|
||||
case 0x0021:
|
||||
case 0x0022:
|
||||
case 0x0023:
|
||||
ISA = "Reserved for future use";
|
||||
break;
|
||||
case 0x000F:
|
||||
ISA = "Hewlett-Packard PA-RISC";
|
||||
break;
|
||||
case 0x0011:
|
||||
ISA = "Fujitsu VPP500";
|
||||
break;
|
||||
case 0x0012:
|
||||
ISA = "Enhanced instruction set SPARC";
|
||||
break;
|
||||
case 0x0013:
|
||||
ISA = "Intel 80960";
|
||||
break;
|
||||
case 0x0014:
|
||||
ISA = "PowerPC";
|
||||
break;
|
||||
case 0x0015:
|
||||
ISA = "PowerPC (64-bit)";
|
||||
break;
|
||||
case 0x0016:
|
||||
ISA = "S390, including S390";
|
||||
break;
|
||||
case 0x0017:
|
||||
ISA = "IBM SPU/SPC";
|
||||
break;
|
||||
case 0x0024:
|
||||
ISA = "NEC V800";
|
||||
break;
|
||||
case 0x0025:
|
||||
ISA = "Fujitsu FR20";
|
||||
break;
|
||||
case 0x0026:
|
||||
ISA = "TRW RH-32";
|
||||
break;
|
||||
case 0x0027:
|
||||
ISA = "Motorola RCE";
|
||||
break;
|
||||
case 0x0028:
|
||||
ISA = "ARM (up to ARMv7/Aarch32)";
|
||||
break;
|
||||
case 0x0029:
|
||||
ISA = "Digital Alpha";
|
||||
break;
|
||||
case 0x002A:
|
||||
ISA = "SuperH";
|
||||
break;
|
||||
case 0x002B:
|
||||
ISA = "SPARC Version 9";
|
||||
break;
|
||||
case 0x002C:
|
||||
ISA = "Siemens TriCore embedded processor";
|
||||
break;
|
||||
case 0x002D:
|
||||
ISA = "Argonaut RISC Core";
|
||||
break;
|
||||
case 0x002E:
|
||||
ISA = "Hitachi H8/300";
|
||||
break;
|
||||
case 0x002F:
|
||||
ISA = "Hitachi H8/300H";
|
||||
break;
|
||||
case 0x0030:
|
||||
ISA = "Hitachi H8S";
|
||||
break;
|
||||
case 0x0031:
|
||||
ISA = "Hitachi H8/500";
|
||||
break;
|
||||
case 0x0032:
|
||||
ISA = "IA-64";
|
||||
break;
|
||||
case 0x0033:
|
||||
ISA = "Standford MIPS-X";
|
||||
break;
|
||||
case 0x0034:
|
||||
ISA = "Motorola ColdFire";
|
||||
break;
|
||||
case 0x0035:
|
||||
ISA = "Motorola M68HC12";
|
||||
break;
|
||||
case 0x0036:
|
||||
ISA = "Fujitsu MMA Multimedia Accelerator";
|
||||
break;
|
||||
case 0x0037:
|
||||
ISA = "Siemens PCP";
|
||||
break;
|
||||
case 0x0038:
|
||||
ISA = "Sony nCPU embedded RISC processor";
|
||||
break;
|
||||
case 0x0039:
|
||||
ISA = "Denso NDR1 microprocessor";
|
||||
break;
|
||||
case 0x003A:
|
||||
ISA = "Motorola Star*Core processor";
|
||||
break;
|
||||
case 0x003B:
|
||||
ISA = "Toyota ME16 processor";
|
||||
break;
|
||||
case 0x003C:
|
||||
ISA = "STMicroelectronics ST100 processor";
|
||||
break;
|
||||
case 0x003D:
|
||||
ISA = "Advanced Logic Corp. TinyJ embedded processor family";
|
||||
break;
|
||||
case 0x003E:
|
||||
ISA = "AMD x86-64";
|
||||
break;
|
||||
case 0x003F:
|
||||
ISA = "Sony DSP Processor";
|
||||
break;
|
||||
case 0x0040:
|
||||
ISA = "Digital Equipment Corp. PDP-10";
|
||||
break;
|
||||
case 0x0041:
|
||||
ISA = "Digital Equipment Corp. PDP-11";
|
||||
break;
|
||||
case 0x0042:
|
||||
ISA = "Siemens FX66 microcontroller";
|
||||
break;
|
||||
case 0x0043:
|
||||
ISA = "STMicroelectronics ST9+ 8/16 bit microcontroller";
|
||||
break;
|
||||
case 0x0044:
|
||||
ISA = "STMicroelectronics ST7 8-bit microcontroller";
|
||||
break;
|
||||
case 0x0045:
|
||||
ISA = "Motorola MC68HC16 Microcontroller";
|
||||
break;
|
||||
case 0x0046:
|
||||
ISA = "Motorola MC68HC11 Microcontroller";
|
||||
break;
|
||||
case 0x0047:
|
||||
ISA = "Motorola MC68HC08 Microcontroller";
|
||||
break;
|
||||
case 0x0048:
|
||||
ISA = "Motorola MC68HC05 Microcontroller";
|
||||
break;
|
||||
case 0x0049:
|
||||
ISA = "Silicon Graphics SVx";
|
||||
break;
|
||||
case 0x004A:
|
||||
ISA = "STMicroelectronics ST19 8-bit microcontroller";
|
||||
break;
|
||||
case 0x004B:
|
||||
ISA = "Digital VAX";
|
||||
break;
|
||||
case 0x004C:
|
||||
ISA = "Axis Communications 32-bit embedded processor";
|
||||
break;
|
||||
case 0x004D:
|
||||
ISA = "Infineon Technologies 32-bit embedded processor";
|
||||
break;
|
||||
case 0x004E:
|
||||
ISA = "Element 14 64-bit DSP Processor";
|
||||
break;
|
||||
case 0x004F:
|
||||
ISA = "LSI Logic 16-bit DSP Processor";
|
||||
break;
|
||||
case 0x0050:
|
||||
ISA = "Donald Knuth's educational 64-bit processor";
|
||||
break;
|
||||
case 0x0051:
|
||||
ISA = "Harvard University machine-independent object files";
|
||||
break;
|
||||
case 0x0052:
|
||||
ISA = "SiTera Prism";
|
||||
break;
|
||||
case 0x0053:
|
||||
ISA = "Atmel AVR 8-bit microcontroller";
|
||||
break;
|
||||
case 0x0054:
|
||||
ISA = "Fujitsu FR30";
|
||||
break;
|
||||
case 0x0055:
|
||||
ISA = "Mitsubishi D10V";
|
||||
break;
|
||||
case 0x0056:
|
||||
ISA = "Mitsubishi D30V";
|
||||
break;
|
||||
case 0x0057:
|
||||
ISA = "NEC v850";
|
||||
break;
|
||||
case 0x0058:
|
||||
ISA = "Mitsubishi M32R";
|
||||
break;
|
||||
case 0x0059:
|
||||
ISA = "Matsushita MN10300";
|
||||
break;
|
||||
case 0x005A:
|
||||
ISA = "Matsushita MN10200";
|
||||
break;
|
||||
case 0x005B:
|
||||
ISA = "picoJava";
|
||||
break;
|
||||
case 0x005C:
|
||||
ISA = "OpenRISC 32-bit embedded processor";
|
||||
break;
|
||||
case 0x005D:
|
||||
ISA = "ARC Cores Tangent-A5";
|
||||
break;
|
||||
case 0x005E:
|
||||
ISA = "Tensilica Xtensa Architecture";
|
||||
break;
|
||||
case 0x005F:
|
||||
ISA = "Alphamosaic VideoCore processor";
|
||||
break;
|
||||
case 0x0060:
|
||||
ISA = "Thompson Multimedia General Purpose Processor";
|
||||
break;
|
||||
case 0x0061:
|
||||
ISA = "National Semiconductor 32000 series";
|
||||
break;
|
||||
case 0x0062:
|
||||
ISA = "Tenor Network TPC processor";
|
||||
break;
|
||||
case 0x0063:
|
||||
ISA = "Trebia SNP 1000 processor";
|
||||
break;
|
||||
case 0x0064:
|
||||
ISA = "STMicroelectronics (www.st.com) ST200 microcontroller";
|
||||
break;
|
||||
case 0x008C:
|
||||
ISA = "TMS320C6000 Family";
|
||||
break;
|
||||
case 0x00AF:
|
||||
ISA = "MCST Elbrus e2k";
|
||||
break;
|
||||
case 0x00B7:
|
||||
ISA = "ARM 64-bits (ARMv8/Aarch64)";
|
||||
break;
|
||||
case 0x00F3:
|
||||
ISA = "RISC-V";
|
||||
break;
|
||||
case 0x00F7:
|
||||
ISA = "Berkeley Packet Filter";
|
||||
break;
|
||||
case 0x0101:
|
||||
ISA = "WDC 65C816";
|
||||
break;
|
||||
default:
|
||||
ISA = "Unimplemented";
|
||||
break;
|
||||
}
|
||||
ehResult.push("Instruction Set Architecture:".padEnd(align) + ISA);
|
||||
|
||||
ehResult.push("ELF Version:".padEnd(align) + `${stream.readInt(4, endianness)}`);
|
||||
|
||||
const readSize = format === 1 ? 4 : 8;
|
||||
entry = stream.readInt(readSize, endianness);
|
||||
phoff = stream.readInt(readSize, endianness);
|
||||
shoff = stream.readInt(readSize, endianness);
|
||||
ehResult.push("Entry Point:".padEnd(align) + `0x${Utils.hex(entry)}`);
|
||||
ehResult.push("Entry PHOFF:".padEnd(align) + `0x${Utils.hex(phoff)}`);
|
||||
ehResult.push("Entry SHOFF:".padEnd(align) + `0x${Utils.hex(shoff)}`);
|
||||
|
||||
const flags = stream.readInt(4, endianness);
|
||||
ehResult.push("Flags:".padEnd(align) + `${Utils.bin(flags)}`);
|
||||
|
||||
ehResult.push("ELF Header Size:".padEnd(align) + `${stream.readInt(2, endianness)} bytes`);
|
||||
ehResult.push("Program Header Size:".padEnd(align) + `${stream.readInt(2, endianness)} bytes`);
|
||||
phEntries = stream.readInt(2, endianness);
|
||||
ehResult.push("Program Header Entries:".padEnd(align) + phEntries);
|
||||
shentSize = stream.readInt(2, endianness);
|
||||
ehResult.push("Section Header Size:".padEnd(align) + shentSize + " bytes");
|
||||
shEntries = stream.readInt(2, endianness);
|
||||
ehResult.push("Section Header Entries:".padEnd(align) + shEntries);
|
||||
shstrtab = stream.readInt(2, endianness);
|
||||
ehResult.push("Section Header Names:".padEnd(align) + shstrtab);
|
||||
|
||||
return ehResult.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* This function parses and extracts relevant information from a Program Header.
|
||||
*
|
||||
* @param {stream} stream
|
||||
* @returns {string}
|
||||
*/
|
||||
function programHeader(stream) {
|
||||
/**
|
||||
* A Program Header is comprised of the following structures depending on the binary's format.
|
||||
*
|
||||
* p_type - 4 Bytes identifying the type of the segment.
|
||||
*
|
||||
* 32-bit:
|
||||
* p_offset - 4 Bytes specifying the offset of the segment.
|
||||
* p_vaddr - 4 Bytes specifying the virtual address of the segment in memory.
|
||||
* p_paddr - 4 Bytes specifying the physical address of the segment in memory.
|
||||
* p_filesz - 4 Bytes specifying the size in bytes of the segment in the file image.
|
||||
* p_memsz - 4 Bytes specifying the size in bytes of the segment in memory.
|
||||
* p_flags - 4 Bytes identifying the segment dependent flags.
|
||||
* p_align - 4 Bytes set to 0 or 1 for alignment or no alignment, respectively.
|
||||
*
|
||||
* 64-bit:
|
||||
* p_flags - 4 Bytes identifying segment dependent flags.
|
||||
* p_offset - 8 Bytes specifying the offset of the segment.
|
||||
* p_vaddr - 8 Bytes specifying the virtual address of the segment in memory.
|
||||
* p_paddr - 8 Bytes specifying the physical address of the segment in memory.
|
||||
* p_filesz - 8 Bytes specifying the size in bytes of the segment in the file image.
|
||||
* p_memsz - 8 Bytes specifying the size in bytes of the segment in memory.
|
||||
* p_align - 8 Bytes set to 0 or 1 for alignment or no alignment, respectively.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This function decodes the flags bitmask for the Program Header.
|
||||
*
|
||||
* @param {integer} flags
|
||||
* @returns {string}
|
||||
*/
|
||||
function readFlags(flags) {
|
||||
const result = [];
|
||||
if (flags & 0x1)
|
||||
result.push("Execute");
|
||||
if (flags & 0x2)
|
||||
result.push("Write");
|
||||
if (flags & 0x4)
|
||||
result.push("Read");
|
||||
if (flags & 0xf0000000)
|
||||
result.push("Unspecified");
|
||||
return result.join(",");
|
||||
}
|
||||
|
||||
const phResult = [];
|
||||
|
||||
let pType = "";
|
||||
const programHeaderType = stream.readInt(4, endianness);
|
||||
switch (true) {
|
||||
case (programHeaderType === 0x00000000):
|
||||
pType = "Unused";
|
||||
break;
|
||||
case (programHeaderType === 0x00000001):
|
||||
pType = "Loadable Segment";
|
||||
break;
|
||||
case (programHeaderType === 0x00000002):
|
||||
pType = "Dynamic linking information";
|
||||
break;
|
||||
case (programHeaderType === 0x00000003):
|
||||
pType = "Interpreter Information";
|
||||
break;
|
||||
case (programHeaderType === 0x00000004):
|
||||
pType = "Auxiliary Information";
|
||||
break;
|
||||
case (programHeaderType === 0x00000005):
|
||||
pType = "Reserved";
|
||||
break;
|
||||
case (programHeaderType === 0x00000006):
|
||||
pType = "Program Header Table";
|
||||
break;
|
||||
case (programHeaderType === 0x00000007):
|
||||
pType = "Thread-Local Storage Template";
|
||||
break;
|
||||
case (programHeaderType >= 0x60000000 && programHeaderType <= 0x6FFFFFFF):
|
||||
pType = "Reserved Inclusive Range. OS Specific";
|
||||
break;
|
||||
case (programHeaderType >= 0x70000000 && programHeaderType <= 0x7FFFFFFF):
|
||||
pType = "Reserved Inclusive Range. Processor Specific";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
phResult.push("Program Header Type:".padEnd(align) + pType);
|
||||
|
||||
if (format === 2)
|
||||
phResult.push("Flags:".padEnd(align) + readFlags(stream.readInt(4, endianness)));
|
||||
|
||||
const readSize = format === 1? 4 : 8;
|
||||
phResult.push("Offset Of Segment:".padEnd(align) + `${stream.readInt(readSize, endianness)}`);
|
||||
phResult.push("Virtual Address of Segment:".padEnd(align) + `${stream.readInt(readSize, endianness)}`);
|
||||
phResult.push("Physical Address of Segment:".padEnd(align) + `${stream.readInt(readSize, endianness)}`);
|
||||
phResult.push("Size of Segment:".padEnd(align) + `${stream.readInt(readSize, endianness)} bytes`);
|
||||
phResult.push("Size of Segment in Memory:".padEnd(align) + `${stream.readInt(readSize, endianness)} bytes`);
|
||||
|
||||
if (format === 1)
|
||||
phResult.push("Flags:".padEnd(align) + readFlags(stream.readInt(4, endianness)));
|
||||
|
||||
stream.moveForwardsBy(readSize);
|
||||
|
||||
return phResult.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* This function parses and extracts relevant information from a Section Header.
|
||||
*
|
||||
* @param {stream} stream
|
||||
* @returns {string}
|
||||
*/
|
||||
function sectionHeader(stream) {
|
||||
/**
|
||||
* A Section Header is comprised of the following structures depending on the binary's format.
|
||||
*
|
||||
* sh_name - 4 Bytes identifying the offset into the .shstrtab for the name of this section.
|
||||
* sh_type - 4 Bytes identifying the type of this header.
|
||||
*
|
||||
* 32-bit:
|
||||
* sh_flags - 4 Bytes identifying section specific flags.
|
||||
* sh_addr - 4 Bytes identifying the virtual address of the section in memory.
|
||||
* sh_offset - 4 Bytes identifying the offset of the section in the file.
|
||||
* sh_size - 4 Bytes specifying the size in bytes of the section in the file image.
|
||||
* sh_link - 4 Bytes identifying the index of an associated section.
|
||||
* sh_info - 4 Bytes specifying extra information about the section.
|
||||
* sh_addralign - 4 Bytes containing the alignment for the section.
|
||||
* sh_entsize - 4 Bytes specifying the size, in bytes, of each entry in the section.
|
||||
*
|
||||
* 64-bit:
|
||||
* sh_flags - 8 Bytes identifying section specific flags.
|
||||
* sh_addr - 8 Bytes identifying the virtual address of the section in memory.
|
||||
* sh_offset - 8 Bytes identifying the offset of the section in the file.
|
||||
* sh_size - 8 Bytes specifying the size in bytes of the section in the file image.
|
||||
* sh_link - 4 Bytes identifying the index of an associated section.
|
||||
* sh_info - 4 Bytes specifying extra information about the section.
|
||||
* sh_addralign - 8 Bytes containing the alignment for the section.
|
||||
* sh_entsize - 8 Bytes specifying the size, in bytes, of each entry in the section.
|
||||
*/
|
||||
const shResult = [];
|
||||
|
||||
const nameOffset = stream.readInt(4, endianness);
|
||||
let type = "";
|
||||
const shType = stream.readInt(4, endianness);
|
||||
switch (true) {
|
||||
case (shType === 0x00000001):
|
||||
type = "Program Data";
|
||||
break;
|
||||
case (shType === 0x00000002):
|
||||
type = "Symbol Table";
|
||||
break;
|
||||
case (shType === 0x00000003):
|
||||
type = "String Table";
|
||||
break;
|
||||
case (shType === 0x00000004):
|
||||
type = "Relocation Entries with Addens";
|
||||
break;
|
||||
case (shType === 0x00000005):
|
||||
type = "Symbol Hash Table";
|
||||
break;
|
||||
case (shType === 0x00000006):
|
||||
type = "Dynamic Linking Information";
|
||||
break;
|
||||
case (shType === 0x00000007):
|
||||
type = "Notes";
|
||||
break;
|
||||
case (shType === 0x00000008):
|
||||
type = "Program Space with No Data";
|
||||
break;
|
||||
case (shType === 0x00000009):
|
||||
type = "Relocation Entries with no Addens";
|
||||
break;
|
||||
case (shType === 0x0000000A):
|
||||
type = "Reserved";
|
||||
break;
|
||||
case (shType === 0x0000000B):
|
||||
type = "Dynamic Linker Symbol Table";
|
||||
break;
|
||||
case (shType === 0x0000000E):
|
||||
type = "Array of Constructors";
|
||||
break;
|
||||
case (shType === 0x0000000F):
|
||||
type = "Array of Destructors";
|
||||
break;
|
||||
case (shType === 0x00000010):
|
||||
type = "Array of pre-constructors";
|
||||
break;
|
||||
case (shType === 0x00000011):
|
||||
type = "Section group";
|
||||
break;
|
||||
case (shType === 0x00000012):
|
||||
type = "Extended section indices";
|
||||
break;
|
||||
case (shType === 0x00000013):
|
||||
type = "Number of defined types";
|
||||
break;
|
||||
case (shType >= 0x60000000 && shType <= 0x6fffffff):
|
||||
type = "OS-specific";
|
||||
break;
|
||||
case (shType >= 0x70000000 && shType <= 0x7fffffff):
|
||||
type = "Processor-specific";
|
||||
break;
|
||||
case (shType >= 0x80000000 && shType <= 0x8fffffff):
|
||||
type = "Application-specific";
|
||||
break;
|
||||
default:
|
||||
type = "Unused";
|
||||
break;
|
||||
}
|
||||
|
||||
shResult.push("Type:".padEnd(align) + type);
|
||||
|
||||
let nameResult = "";
|
||||
if (type !== "Unused") {
|
||||
nameResult = readString(stream, namesOffset, nameOffset);
|
||||
shResult.push("Section Name: ".padEnd(align) + nameResult);
|
||||
}
|
||||
|
||||
const readSize = (format === 1) ? 4 : 8;
|
||||
|
||||
const flags = stream.readInt(readSize, endianness);
|
||||
const shFlags = [];
|
||||
const bitMasks = [
|
||||
[0x00000001, "Writable"],
|
||||
[0x00000002, "Alloc"],
|
||||
[0x00000004, "Executable"],
|
||||
[0x00000010, "Merge"],
|
||||
[0x00000020, "Strings"],
|
||||
[0x00000040, "SHT Info Link"],
|
||||
[0x00000080, "Link Order"],
|
||||
[0x00000100, "OS Specific Handling"],
|
||||
[0x00000200, "Group"],
|
||||
[0x00000400, "Thread Local Data"],
|
||||
[0x0FF00000, "OS-Specific"],
|
||||
[0xF0000000, "Processor Specific"],
|
||||
[0x04000000, "Special Ordering (Solaris)"],
|
||||
[0x08000000, "Excluded (Solaris)"]
|
||||
];
|
||||
bitMasks.forEach(elem => {
|
||||
if (flags & elem[0])
|
||||
shFlags.push(elem[1]);
|
||||
});
|
||||
shResult.push("Flags:".padEnd(align) + shFlags);
|
||||
|
||||
const vaddr = stream.readInt(readSize, endianness);
|
||||
shResult.push("Section Vaddr in memory:".padEnd(align) + vaddr);
|
||||
|
||||
const shoffset = stream.readInt(readSize, endianness);
|
||||
shResult.push("Offset of the section:".padEnd(align) + shoffset);
|
||||
|
||||
const secSize = stream.readInt(readSize, endianness);
|
||||
shResult.push("Section Size:".padEnd(align) + secSize);
|
||||
|
||||
const associatedSection = stream.readInt(4, endianness);
|
||||
shResult.push("Associated Section:".padEnd(align) + associatedSection);
|
||||
|
||||
const extraInfo = stream.readInt(4, endianness);
|
||||
shResult.push("Section Extra Information:".padEnd(align) + extraInfo);
|
||||
|
||||
// Jump over alignment field.
|
||||
stream.moveForwardsBy(readSize);
|
||||
const entSize = stream.readInt(readSize, endianness);
|
||||
switch (nameResult) {
|
||||
case ".strtab":
|
||||
strtabOffset = shoffset;
|
||||
break;
|
||||
case ".symtab":
|
||||
symtabOffset = shoffset;
|
||||
symtabSize = secSize;
|
||||
symtabEntSize = entSize;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return shResult.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns the offset of the Section Header Names Section.
|
||||
*
|
||||
* @param {stream} stream
|
||||
*/
|
||||
function getNamesOffset(stream) {
|
||||
const preMove = stream.position;
|
||||
stream.moveTo(shoff + (shentSize * shstrtab));
|
||||
if (format === 1) {
|
||||
stream.moveForwardsBy(0x10);
|
||||
namesOffset = stream.readInt(4, endianness);
|
||||
} else {
|
||||
stream.moveForwardsBy(0x18);
|
||||
namesOffset = stream.readInt(8, endianness);
|
||||
}
|
||||
stream.position = preMove;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns a symbol's name from the string table.
|
||||
*
|
||||
* @param {stream} stream
|
||||
* @returns {string}
|
||||
*/
|
||||
function getSymbols(stream) {
|
||||
/**
|
||||
* The Symbol Table is comprised of Symbol Table Entries whose structure depends on the binary's format.
|
||||
*
|
||||
* 32-bit:
|
||||
* st_name - 4 Bytes specifying an index in the files symbol string table.
|
||||
* st_value - 4 Bytes identifying the value associated with the symbol.
|
||||
* st_size - 4 Bytes specifying the size associated with the symbol (this is not the size of the symbol).
|
||||
* st_info - A byte specifying the type and binding of the symbol.
|
||||
* st_other - A byte specifying the symbol's visibility.
|
||||
* st_shndx - 2 Bytes identifying the section that this symbol is related to.
|
||||
*
|
||||
* 64-bit:
|
||||
* st_name - 4 Bytes specifying an index in the files symbol string table.
|
||||
* st_info - A byte specifying the type and binding of the symbol.
|
||||
* st_other - A byte specifying the symbol's visibility.
|
||||
* st_shndx - 2 Bytes identifying the section that this symbol is related to.
|
||||
* st_value - 8 Bytes identifying the value associated with the symbol.
|
||||
* st_size - 8 Bytes specifying the size associated with the symbol (this is not the size of the symbol).
|
||||
*/
|
||||
const nameOffset = stream.readInt(4, endianness);
|
||||
stream.moveForwardsBy(format === 2 ? 20 : 12);
|
||||
return readString(stream, strtabOffset, nameOffset);
|
||||
}
|
||||
|
||||
input = new Uint8Array(input);
|
||||
const stream = new Stream(input);
|
||||
const result = ["=".repeat(align) + " ELF Header " + "=".repeat(align)];
|
||||
result.push(elfHeader(stream) + "\n");
|
||||
|
||||
getNamesOffset(stream);
|
||||
|
||||
result.push("=".repeat(align) + " Program Header " + "=".repeat(align));
|
||||
stream.moveTo(phoff);
|
||||
for (let i = 0; i < phEntries; i++)
|
||||
result.push(programHeader(stream) + "\n");
|
||||
|
||||
result.push("=".repeat(align) + " Section Header " + "=".repeat(align));
|
||||
stream.moveTo(shoff);
|
||||
for (let i = 0; i < shEntries; i++)
|
||||
result.push(sectionHeader(stream) + "\n");
|
||||
|
||||
result.push("=".repeat(align) + " Symbol Table " + "=".repeat(align));
|
||||
|
||||
stream.moveTo(symtabOffset);
|
||||
let elem = "";
|
||||
for (let i = 0; i < (symtabSize / symtabEntSize); i++)
|
||||
if ((elem = getSymbols(stream)) !== "")
|
||||
result.push("Symbol Name:".padEnd(align) + elem);
|
||||
|
||||
return result.join("\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ELFInfo;
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ class ExtractFiles extends Operation {
|
||||
<li>
|
||||
${supportedExts.join("</li><li>")}
|
||||
</li>
|
||||
</ul>`;
|
||||
</ul>Minimum File Size can be used to prune small false positives.`;
|
||||
this.infoURL = "https://forensicswiki.xyz/wiki/index.php?title=File_Carving";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "List<File>";
|
||||
@@ -54,6 +54,11 @@ class ExtractFiles extends Operation {
|
||||
name: "Ignore failed extractions",
|
||||
type: "boolean",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: "Minimum File Size",
|
||||
type: "number",
|
||||
value: 100
|
||||
}
|
||||
]);
|
||||
}
|
||||
@@ -66,6 +71,7 @@ class ExtractFiles extends Operation {
|
||||
run(input, args) {
|
||||
const bytes = new Uint8Array(input),
|
||||
categories = [],
|
||||
minSize = args.pop(1),
|
||||
ignoreFailedExtractions = args.pop(1);
|
||||
|
||||
args.forEach((cat, i) => {
|
||||
@@ -80,7 +86,9 @@ class ExtractFiles extends Operation {
|
||||
const errors = [];
|
||||
detectedFiles.forEach(detectedFile => {
|
||||
try {
|
||||
files.push(extractFile(bytes, detectedFile.fileDetails, detectedFile.offset));
|
||||
const file = extractFile(bytes, detectedFile.fileDetails, detectedFile.offset);
|
||||
if (file.size >= minSize)
|
||||
files.push(file);
|
||||
} catch (err) {
|
||||
if (!ignoreFailedExtractions && err.message.indexOf("No extraction algorithm available") < 0) {
|
||||
errors.push(
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,10 +35,18 @@ class Fletcher32Checksum extends Operation {
|
||||
run(input, args) {
|
||||
let a = 0,
|
||||
b = 0;
|
||||
input = new Uint8Array(input);
|
||||
if (ArrayBuffer.isView(input)) {
|
||||
input = new DataView(input.buffer, input.byteOffset, input.byteLength);
|
||||
} else {
|
||||
input = new DataView(input);
|
||||
}
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
a = (a + input[i]) % 0xffff;
|
||||
for (let i = 0; i < input.byteLength - 1; i += 2) {
|
||||
a = (a + input.getUint16(i, true)) % 0xffff;
|
||||
b = (b + a) % 0xffff;
|
||||
}
|
||||
if (input.byteLength % 2 !== 0) {
|
||||
a = (a + input.getUint8(input.byteLength - 1)) % 0xffff;
|
||||
b = (b + a) % 0xffff;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,10 +35,22 @@ class Fletcher64Checksum extends Operation {
|
||||
run(input, args) {
|
||||
let a = 0,
|
||||
b = 0;
|
||||
input = new Uint8Array(input);
|
||||
if (ArrayBuffer.isView(input)) {
|
||||
input = new DataView(input.buffer, input.byteOffset, input.byteLength);
|
||||
} else {
|
||||
input = new DataView(input);
|
||||
}
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
a = (a + input[i]) % 0xffffffff;
|
||||
for (let i = 0; i < input.byteLength - 3; i += 4) {
|
||||
a = (a + input.getUint32(i, true)) % 0xffffffff;
|
||||
b = (b + a) % 0xffffffff;
|
||||
}
|
||||
if (input.byteLength % 4 !== 0) {
|
||||
let lastValue = 0;
|
||||
for (let i = 0; i < input.byteLength % 4; i++) {
|
||||
lastValue = (lastValue << 8) | input.getUint8(input.byteLength - 1 - i);
|
||||
}
|
||||
a = (a + lastValue) % 0xffffffff;
|
||||
b = (b + a) % 0xffffffff;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,12 +65,21 @@ class Fork extends Operation {
|
||||
if (input)
|
||||
inputs = input.split(splitDelim);
|
||||
|
||||
// Set to 1 as if we are here, then there is one, the current one.
|
||||
let numOp = 1;
|
||||
// Create subOpList for each tranche to operate on
|
||||
// (all remaining operations unless we encounter a Merge)
|
||||
// all remaining operations unless we encounter a Merge
|
||||
for (i = state.progress + 1; i < opList.length; i++) {
|
||||
if (opList[i].name === "Merge" && !opList[i].disabled) {
|
||||
break;
|
||||
numOp--;
|
||||
if (numOp === 0 || opList[i].ingValues[0])
|
||||
break;
|
||||
else
|
||||
// Not this Fork's Merge.
|
||||
subOpList.push(opList[i]);
|
||||
} else {
|
||||
if (opList[i].name === "Fork" || opList[i].name === "Subsection")
|
||||
numOp++;
|
||||
subOpList.push(opList[i]);
|
||||
}
|
||||
}
|
||||
|
||||
101
src/core/operations/FromBase45.mjs
Normal file
101
src/core/operations/FromBase45.mjs
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* @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
|
||||
},
|
||||
{
|
||||
name: "Remove non-alphabet chars",
|
||||
type: "boolean",
|
||||
value: true
|
||||
},
|
||||
];
|
||||
|
||||
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]).join("");
|
||||
const removeNonAlphChars = args[1];
|
||||
|
||||
const res = [];
|
||||
|
||||
// Remove non-alphabet characters
|
||||
if (removeNonAlphChars) {
|
||||
const re = new RegExp("[^" + alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g");
|
||||
input = input.replace(re, "");
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,6 +32,40 @@ class FromBase85 extends Operation {
|
||||
type: "editableOption",
|
||||
value: ALPHABET_OPTIONS
|
||||
},
|
||||
{
|
||||
name: "Remove non-alphabet chars",
|
||||
type: "boolean",
|
||||
value: true
|
||||
},
|
||||
];
|
||||
this.checks = [
|
||||
{
|
||||
pattern:
|
||||
"^\\s*(?:<~)?" + // Optional whitespace and starting marker
|
||||
"[\\s!-uz]*" + // Any amount of base85 characters and whitespace
|
||||
"[!-uz]{15}" + // At least 15 continoues base85 characters without whitespace
|
||||
"[\\s!-uz]*" + // Any amount of base85 characters and whitespace
|
||||
"(?:~>)?\\s*$", // Optional ending marker and whitespace
|
||||
args: ["!-u"],
|
||||
},
|
||||
{
|
||||
pattern:
|
||||
"^" +
|
||||
"[\\s0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]*" +
|
||||
"[0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]{15}" + // At least 15 continoues base85 characters without whitespace
|
||||
"[\\s0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]*" +
|
||||
"$",
|
||||
args: ["0-9a-zA-Z.\\-:+=^!/*?&<>()[]{}@%$#"],
|
||||
},
|
||||
{
|
||||
pattern:
|
||||
"^" +
|
||||
"[\\s0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]*" +
|
||||
"[0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]{15}" + // At least 15 continoues base85 characters without whitespace
|
||||
"[\\s0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]*" +
|
||||
"$",
|
||||
args: ["0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~"],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -43,6 +77,7 @@ class FromBase85 extends Operation {
|
||||
run(input, args) {
|
||||
const alphabet = Utils.expandAlphRange(args[0]).join(""),
|
||||
encoding = alphabetName(alphabet),
|
||||
removeNonAlphChars = args[1],
|
||||
result = [];
|
||||
|
||||
if (alphabet.length !== 85 ||
|
||||
@@ -50,11 +85,18 @@ class FromBase85 extends Operation {
|
||||
throw new OperationError("Alphabet must be of length 85");
|
||||
}
|
||||
|
||||
if (input.length === 0) return [];
|
||||
|
||||
const matches = input.match(/<~(.+?)~>/);
|
||||
// Remove delimiters if present
|
||||
const matches = input.match(/^<~(.+?)~>$/);
|
||||
if (matches !== null) input = matches[1];
|
||||
|
||||
// Remove non-alphabet characters
|
||||
if (removeNonAlphChars) {
|
||||
const re = new RegExp("[^" + alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g");
|
||||
input = input.replace(re, "");
|
||||
}
|
||||
|
||||
if (input.length === 0) return [];
|
||||
|
||||
let i = 0;
|
||||
let block, blockBytes;
|
||||
while (i < input.length) {
|
||||
@@ -69,7 +111,7 @@ class FromBase85 extends Operation {
|
||||
.map((chr, idx) => {
|
||||
const digit = alphabet.indexOf(chr);
|
||||
if (digit < 0 || digit > 84) {
|
||||
throw `Invalid character '${chr}' at index ${idx}`;
|
||||
throw `Invalid character '${chr}' at index ${i + idx}`;
|
||||
}
|
||||
return digit;
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @author john19696 [john19696@protonmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
@@ -33,6 +34,9 @@ import BLAKE2b from "./BLAKE2b.mjs";
|
||||
import BLAKE2s from "./BLAKE2s.mjs";
|
||||
import Streebog from "./Streebog.mjs";
|
||||
import GOSTHash from "./GOSTHash.mjs";
|
||||
import LMHash from "./LMHash.mjs";
|
||||
import NTHash from "./NTHash.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* Generate all hashes operation
|
||||
@@ -51,7 +55,75 @@ class GenerateAllHashes extends Operation {
|
||||
this.infoURL = "https://wikipedia.org/wiki/Comparison_of_cryptographic_hash_functions";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
this.args = [
|
||||
{
|
||||
name: "Length (bits)",
|
||||
type: "option",
|
||||
value: [
|
||||
"All", "128", "160", "224", "256", "320", "384", "512"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Include names",
|
||||
type: "boolean",
|
||||
value: true
|
||||
},
|
||||
];
|
||||
this.hashes = [
|
||||
{name: "MD2", algo: (new MD2()), inputType: "arrayBuffer", params: []},
|
||||
{name: "MD4", algo: (new MD4()), inputType: "arrayBuffer", params: []},
|
||||
{name: "MD5", algo: (new MD5()), inputType: "arrayBuffer", params: []},
|
||||
{name: "MD6", algo: (new MD6()), inputType: "str", params: []},
|
||||
{name: "SHA0", algo: (new SHA0()), inputType: "arrayBuffer", params: []},
|
||||
{name: "SHA1", algo: (new SHA1()), inputType: "arrayBuffer", params: []},
|
||||
{name: "SHA2 224", algo: (new SHA2()), inputType: "arrayBuffer", params: ["224"]},
|
||||
{name: "SHA2 256", algo: (new SHA2()), inputType: "arrayBuffer", params: ["256"]},
|
||||
{name: "SHA2 384", algo: (new SHA2()), inputType: "arrayBuffer", params: ["384"]},
|
||||
{name: "SHA2 512", algo: (new SHA2()), inputType: "arrayBuffer", params: ["512"]},
|
||||
{name: "SHA3 224", algo: (new SHA3()), inputType: "arrayBuffer", params: ["224"]},
|
||||
{name: "SHA3 256", algo: (new SHA3()), inputType: "arrayBuffer", params: ["256"]},
|
||||
{name: "SHA3 384", algo: (new SHA3()), inputType: "arrayBuffer", params: ["384"]},
|
||||
{name: "SHA3 512", algo: (new SHA3()), inputType: "arrayBuffer", params: ["512"]},
|
||||
{name: "Keccak 224", algo: (new Keccak()), inputType: "arrayBuffer", params: ["224"]},
|
||||
{name: "Keccak 256", algo: (new Keccak()), inputType: "arrayBuffer", params: ["256"]},
|
||||
{name: "Keccak 384", algo: (new Keccak()), inputType: "arrayBuffer", params: ["384"]},
|
||||
{name: "Keccak 512", algo: (new Keccak()), inputType: "arrayBuffer", params: ["512"]},
|
||||
{name: "Shake 128", algo: (new Shake()), inputType: "arrayBuffer", params: ["128", 256]},
|
||||
{name: "Shake 256", algo: (new Shake()), inputType: "arrayBuffer", params: ["256", 512]},
|
||||
{name: "RIPEMD-128", algo: (new RIPEMD()), inputType: "arrayBuffer", params: ["128"]},
|
||||
{name: "RIPEMD-160", algo: (new RIPEMD()), inputType: "arrayBuffer", params: ["160"]},
|
||||
{name: "RIPEMD-256", algo: (new RIPEMD()), inputType: "arrayBuffer", params: ["256"]},
|
||||
{name: "RIPEMD-320", algo: (new RIPEMD()), inputType: "arrayBuffer", params: ["320"]},
|
||||
{name: "HAS-160", algo: (new HAS160()), inputType: "arrayBuffer", params: []},
|
||||
{name: "Whirlpool-0", algo: (new Whirlpool()), inputType: "arrayBuffer", params: ["Whirlpool-0"]},
|
||||
{name: "Whirlpool-T", algo: (new Whirlpool()), inputType: "arrayBuffer", params: ["Whirlpool-T"]},
|
||||
{name: "Whirlpool", algo: (new Whirlpool()), inputType: "arrayBuffer", params: ["Whirlpool"]},
|
||||
{name: "BLAKE2b-128", algo: (new BLAKE2b), inputType: "arrayBuffer", params: ["128", "Hex", {string: "", option: "UTF8"}]},
|
||||
{name: "BLAKE2b-160", algo: (new BLAKE2b), inputType: "arrayBuffer", params: ["160", "Hex", {string: "", option: "UTF8"}]},
|
||||
{name: "BLAKE2b-256", algo: (new BLAKE2b), inputType: "arrayBuffer", params: ["256", "Hex", {string: "", option: "UTF8"}]},
|
||||
{name: "BLAKE2b-384", algo: (new BLAKE2b), inputType: "arrayBuffer", params: ["384", "Hex", {string: "", option: "UTF8"}]},
|
||||
{name: "BLAKE2b-512", algo: (new BLAKE2b), inputType: "arrayBuffer", params: ["512", "Hex", {string: "", option: "UTF8"}]},
|
||||
{name: "BLAKE2s-128", algo: (new BLAKE2s), inputType: "arrayBuffer", params: ["128", "Hex", {string: "", option: "UTF8"}]},
|
||||
{name: "BLAKE2s-160", algo: (new BLAKE2s), inputType: "arrayBuffer", params: ["160", "Hex", {string: "", option: "UTF8"}]},
|
||||
{name: "BLAKE2s-256", algo: (new BLAKE2s), inputType: "arrayBuffer", params: ["256", "Hex", {string: "", option: "UTF8"}]},
|
||||
{name: "Streebog-256", algo: (new Streebog), inputType: "arrayBuffer", params: ["256"]},
|
||||
{name: "Streebog-512", algo: (new Streebog), inputType: "arrayBuffer", params: ["512"]},
|
||||
{name: "GOST", algo: (new GOSTHash), inputType: "arrayBuffer", params: ["D-A"]},
|
||||
{name: "LM Hash", algo: (new LMHash), inputType: "str", params: []},
|
||||
{name: "NT Hash", algo: (new NTHash), inputType: "str", params: []},
|
||||
{name: "SSDEEP", algo: (new SSDEEP()), inputType: "str"},
|
||||
{name: "CTPH", algo: (new CTPH()), inputType: "str"}
|
||||
];
|
||||
this.checksums = [
|
||||
{name: "Fletcher-8", algo: (new Fletcher8Checksum), inputType: "byteArray", params: []},
|
||||
{name: "Fletcher-16", algo: (new Fletcher16Checksum), inputType: "byteArray", params: []},
|
||||
{name: "Fletcher-32", algo: (new Fletcher32Checksum), inputType: "byteArray", params: []},
|
||||
{name: "Fletcher-64", algo: (new Fletcher64Checksum), inputType: "byteArray", params: []},
|
||||
{name: "Adler-32", algo: (new Adler32Checksum), inputType: "byteArray", params: []},
|
||||
{name: "CRC-8", algo: (new CRC8Checksum), inputType: "arrayBuffer", params: ["CRC-8"]},
|
||||
{name: "CRC-16", algo: (new CRC16Checksum), inputType: "arrayBuffer", params: []},
|
||||
{name: "CRC-32", algo: (new CRC32Checksum), inputType: "arrayBuffer", params: []}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,63 +132,74 @@ class GenerateAllHashes extends Operation {
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const arrayBuffer = input,
|
||||
str = Utils.arrayBufferToStr(arrayBuffer, false),
|
||||
byteArray = new Uint8Array(arrayBuffer),
|
||||
output = "MD2: " + (new MD2()).run(arrayBuffer, []) +
|
||||
"\nMD4: " + (new MD4()).run(arrayBuffer, []) +
|
||||
"\nMD5: " + (new MD5()).run(arrayBuffer, []) +
|
||||
"\nMD6: " + (new MD6()).run(str, []) +
|
||||
"\nSHA0: " + (new SHA0()).run(arrayBuffer, []) +
|
||||
"\nSHA1: " + (new SHA1()).run(arrayBuffer, []) +
|
||||
"\nSHA2 224: " + (new SHA2()).run(arrayBuffer, ["224"]) +
|
||||
"\nSHA2 256: " + (new SHA2()).run(arrayBuffer, ["256"]) +
|
||||
"\nSHA2 384: " + (new SHA2()).run(arrayBuffer, ["384"]) +
|
||||
"\nSHA2 512: " + (new SHA2()).run(arrayBuffer, ["512"]) +
|
||||
"\nSHA3 224: " + (new SHA3()).run(arrayBuffer, ["224"]) +
|
||||
"\nSHA3 256: " + (new SHA3()).run(arrayBuffer, ["256"]) +
|
||||
"\nSHA3 384: " + (new SHA3()).run(arrayBuffer, ["384"]) +
|
||||
"\nSHA3 512: " + (new SHA3()).run(arrayBuffer, ["512"]) +
|
||||
"\nKeccak 224: " + (new Keccak()).run(arrayBuffer, ["224"]) +
|
||||
"\nKeccak 256: " + (new Keccak()).run(arrayBuffer, ["256"]) +
|
||||
"\nKeccak 384: " + (new Keccak()).run(arrayBuffer, ["384"]) +
|
||||
"\nKeccak 512: " + (new Keccak()).run(arrayBuffer, ["512"]) +
|
||||
"\nShake 128: " + (new Shake()).run(arrayBuffer, ["128", 256]) +
|
||||
"\nShake 256: " + (new Shake()).run(arrayBuffer, ["256", 512]) +
|
||||
"\nRIPEMD-128: " + (new RIPEMD()).run(arrayBuffer, ["128"]) +
|
||||
"\nRIPEMD-160: " + (new RIPEMD()).run(arrayBuffer, ["160"]) +
|
||||
"\nRIPEMD-256: " + (new RIPEMD()).run(arrayBuffer, ["256"]) +
|
||||
"\nRIPEMD-320: " + (new RIPEMD()).run(arrayBuffer, ["320"]) +
|
||||
"\nHAS-160: " + (new HAS160()).run(arrayBuffer, []) +
|
||||
"\nWhirlpool-0: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool-0"]) +
|
||||
"\nWhirlpool-T: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool-T"]) +
|
||||
"\nWhirlpool: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool"]) +
|
||||
"\nBLAKE2b-128: " + (new BLAKE2b).run(arrayBuffer, ["128", "Hex", {string: "", option: "UTF8"}]) +
|
||||
"\nBLAKE2b-160: " + (new BLAKE2b).run(arrayBuffer, ["160", "Hex", {string: "", option: "UTF8"}]) +
|
||||
"\nBLAKE2b-256: " + (new BLAKE2b).run(arrayBuffer, ["256", "Hex", {string: "", option: "UTF8"}]) +
|
||||
"\nBLAKE2b-384: " + (new BLAKE2b).run(arrayBuffer, ["384", "Hex", {string: "", option: "UTF8"}]) +
|
||||
"\nBLAKE2b-512: " + (new BLAKE2b).run(arrayBuffer, ["512", "Hex", {string: "", option: "UTF8"}]) +
|
||||
"\nBLAKE2s-128: " + (new BLAKE2s).run(arrayBuffer, ["128", "Hex", {string: "", option: "UTF8"}]) +
|
||||
"\nBLAKE2s-160: " + (new BLAKE2s).run(arrayBuffer, ["160", "Hex", {string: "", option: "UTF8"}]) +
|
||||
"\nBLAKE2s-256: " + (new BLAKE2s).run(arrayBuffer, ["256", "Hex", {string: "", option: "UTF8"}]) +
|
||||
"\nStreebog-256: " + (new Streebog).run(arrayBuffer, ["256"]) +
|
||||
"\nStreebog-512: " + (new Streebog).run(arrayBuffer, ["512"]) +
|
||||
"\nGOST: " + (new GOSTHash).run(arrayBuffer, ["D-A"]) +
|
||||
"\nSSDEEP: " + (new SSDEEP()).run(str) +
|
||||
"\nCTPH: " + (new CTPH()).run(str) +
|
||||
"\n\nChecksums:" +
|
||||
"\nFletcher-8: " + (new Fletcher8Checksum).run(byteArray, []) +
|
||||
"\nFletcher-16: " + (new Fletcher16Checksum).run(byteArray, []) +
|
||||
"\nFletcher-32: " + (new Fletcher32Checksum).run(byteArray, []) +
|
||||
"\nFletcher-64: " + (new Fletcher64Checksum).run(byteArray, []) +
|
||||
"\nAdler-32: " + (new Adler32Checksum).run(byteArray, []) +
|
||||
"\nCRC-8: " + (new CRC8Checksum).run(arrayBuffer, ["CRC-8"]) +
|
||||
"\nCRC-16: " + (new CRC16Checksum).run(arrayBuffer, []) +
|
||||
"\nCRC-32: " + (new CRC32Checksum).run(arrayBuffer, []);
|
||||
const [length, includeNames] = args;
|
||||
this.inputArrayBuffer = input;
|
||||
this.inputStr = Utils.arrayBufferToStr(input, false);
|
||||
this.inputByteArray = new Uint8Array(input);
|
||||
|
||||
let digest, output = "";
|
||||
// iterate over each of the hashes
|
||||
this.hashes.forEach(hash => {
|
||||
digest = this.executeAlgo(hash.algo, hash.inputType, hash.params || []);
|
||||
output += this.formatDigest(digest, length, includeNames, hash.name);
|
||||
});
|
||||
|
||||
if (length === "All") {
|
||||
output += "\nChecksums:\n";
|
||||
this.checksums.forEach(checksum => {
|
||||
digest = this.executeAlgo(checksum.algo, checksum.inputType, checksum.params || []);
|
||||
output += this.formatDigest(digest, length, includeNames, checksum.name);
|
||||
});
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a hash or checksum algorithm
|
||||
*
|
||||
* @param {Function} algo - The hash or checksum algorithm
|
||||
* @param {string} inputType
|
||||
* @param {Object[]} [params=[]]
|
||||
* @returns {string}
|
||||
*/
|
||||
executeAlgo(algo, inputType, params=[]) {
|
||||
let digest = null;
|
||||
switch (inputType) {
|
||||
case "arrayBuffer":
|
||||
digest = algo.run(this.inputArrayBuffer, params);
|
||||
break;
|
||||
case "str":
|
||||
digest = algo.run(this.inputStr, params);
|
||||
break;
|
||||
case "byteArray":
|
||||
digest = algo.run(this.inputByteArray, params);
|
||||
break;
|
||||
default:
|
||||
throw new OperationError("Unknown hash input type: " + inputType);
|
||||
}
|
||||
|
||||
return digest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the digest depending on user-specified arguments
|
||||
* @param {string} digest
|
||||
* @param {string} length
|
||||
* @param {boolean} includeNames
|
||||
* @param {string} name
|
||||
* @returns {string}
|
||||
*/
|
||||
formatDigest(digest, length, includeNames, name) {
|
||||
if (length !== "All" && (digest.length * 4) !== parseInt(length, 10))
|
||||
return "";
|
||||
|
||||
if (!includeNames)
|
||||
return digest + "\n";
|
||||
|
||||
return `${name}:${" ".repeat(13-name.length)}${digest}\n`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default GenerateAllHashes;
|
||||
|
||||
@@ -44,7 +44,7 @@ class GenerateQRCode extends Operation {
|
||||
{
|
||||
"name": "Margin (num modules)",
|
||||
"type": "number",
|
||||
"value": 2,
|
||||
"value": 4,
|
||||
"min": 0
|
||||
},
|
||||
{
|
||||
|
||||
53
src/core/operations/GetAllCasings.mjs
Normal file
53
src/core/operations/GetAllCasings.mjs
Normal 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;
|
||||
166
src/core/operations/HASSHClientFingerprint.mjs
Normal file
166
src/core/operations/HASSHClientFingerprint.mjs
Normal file
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2021
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* HASSH created by Salesforce
|
||||
* Ben Reardon (@benreardon)
|
||||
* Adel Karimi (@0x4d31)
|
||||
* and the JA3 crew:
|
||||
* John B. Althouse
|
||||
* Jeff Atkinson
|
||||
* Josh Atkins
|
||||
*
|
||||
* Algorithm released under the BSD-3-clause licence
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import Stream from "../lib/Stream.mjs";
|
||||
import {runHash} from "../lib/Hash.mjs";
|
||||
|
||||
/**
|
||||
* HASSH Client Fingerprint operation
|
||||
*/
|
||||
class HASSHClientFingerprint extends Operation {
|
||||
|
||||
/**
|
||||
* HASSHClientFingerprint constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "HASSH Client Fingerprint";
|
||||
this.module = "Crypto";
|
||||
this.description = "Generates a HASSH fingerprint to help identify SSH clients based on hashing together values from the Client Key Exchange Init message.<br><br>Input: A hex stream of the SSH_MSG_KEXINIT packet application layer from Client to Server.";
|
||||
this.infoURL = "https://engineering.salesforce.com/open-sourcing-hassh-abed3ae5044c";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Input format",
|
||||
type: "option",
|
||||
value: ["Hex", "Base64", "Raw"]
|
||||
},
|
||||
{
|
||||
name: "Output format",
|
||||
type: "option",
|
||||
value: ["Hash digest", "HASSH algorithms string", "Full details"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [inputFormat, outputFormat] = args;
|
||||
|
||||
input = Utils.convertToByteArray(input, inputFormat);
|
||||
const s = new Stream(new Uint8Array(input));
|
||||
|
||||
// Length
|
||||
const length = s.readInt(4);
|
||||
if (s.length !== length + 4)
|
||||
throw new OperationError("Incorrect packet length.");
|
||||
|
||||
// Padding length
|
||||
const paddingLength = s.readInt(1);
|
||||
|
||||
// Message code
|
||||
const messageCode = s.readInt(1);
|
||||
if (messageCode !== 20)
|
||||
throw new OperationError("Not a Key Exchange Init.");
|
||||
|
||||
// Cookie
|
||||
s.moveForwardsBy(16);
|
||||
|
||||
// KEX Algorithms
|
||||
const kexAlgosLength = s.readInt(4);
|
||||
const kexAlgos = s.readString(kexAlgosLength);
|
||||
|
||||
// Server Host Key Algorithms
|
||||
const serverHostKeyAlgosLength = s.readInt(4);
|
||||
s.moveForwardsBy(serverHostKeyAlgosLength);
|
||||
|
||||
// Encryption Algorithms Client to Server
|
||||
const encAlgosC2SLength = s.readInt(4);
|
||||
const encAlgosC2S = s.readString(encAlgosC2SLength);
|
||||
|
||||
// Encryption Algorithms Server to Client
|
||||
const encAlgosS2CLength = s.readInt(4);
|
||||
s.moveForwardsBy(encAlgosS2CLength);
|
||||
|
||||
// MAC Algorithms Client to Server
|
||||
const macAlgosC2SLength = s.readInt(4);
|
||||
const macAlgosC2S = s.readString(macAlgosC2SLength);
|
||||
|
||||
// MAC Algorithms Server to Client
|
||||
const macAlgosS2CLength = s.readInt(4);
|
||||
s.moveForwardsBy(macAlgosS2CLength);
|
||||
|
||||
// Compression Algorithms Client to Server
|
||||
const compAlgosC2SLength = s.readInt(4);
|
||||
const compAlgosC2S = s.readString(compAlgosC2SLength);
|
||||
|
||||
// Compression Algorithms Server to Client
|
||||
const compAlgosS2CLength = s.readInt(4);
|
||||
s.moveForwardsBy(compAlgosS2CLength);
|
||||
|
||||
// Languages Client to Server
|
||||
const langsC2SLength = s.readInt(4);
|
||||
s.moveForwardsBy(langsC2SLength);
|
||||
|
||||
// Languages Server to Client
|
||||
const langsS2CLength = s.readInt(4);
|
||||
s.moveForwardsBy(langsS2CLength);
|
||||
|
||||
// First KEX packet follows
|
||||
s.moveForwardsBy(1);
|
||||
|
||||
// Reserved
|
||||
s.moveForwardsBy(4);
|
||||
|
||||
// Padding string
|
||||
s.moveForwardsBy(paddingLength);
|
||||
|
||||
// Output
|
||||
const hassh = [
|
||||
kexAlgos,
|
||||
encAlgosC2S,
|
||||
macAlgosC2S,
|
||||
compAlgosC2S
|
||||
];
|
||||
const hasshStr = hassh.join(";");
|
||||
const hasshHash = runHash("md5", Utils.strToArrayBuffer(hasshStr));
|
||||
|
||||
switch (outputFormat) {
|
||||
case "HASSH algorithms string":
|
||||
return hasshStr;
|
||||
case "Full details":
|
||||
return `Hash digest:
|
||||
${hasshHash}
|
||||
|
||||
Full HASSH algorithms string:
|
||||
${hasshStr}
|
||||
|
||||
Key Exchange Algorithms:
|
||||
${kexAlgos}
|
||||
Encryption Algorithms Client to Server:
|
||||
${encAlgosC2S}
|
||||
MAC Algorithms Client to Server:
|
||||
${macAlgosC2S}
|
||||
Compression Algorithms Client to Server:
|
||||
${compAlgosC2S}`;
|
||||
case "Hash digest":
|
||||
default:
|
||||
return hasshHash;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default HASSHClientFingerprint;
|
||||
166
src/core/operations/HASSHServerFingerprint.mjs
Normal file
166
src/core/operations/HASSHServerFingerprint.mjs
Normal file
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2021
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* HASSH created by Salesforce
|
||||
* Ben Reardon (@benreardon)
|
||||
* Adel Karimi (@0x4d31)
|
||||
* and the JA3 crew:
|
||||
* John B. Althouse
|
||||
* Jeff Atkinson
|
||||
* Josh Atkins
|
||||
*
|
||||
* Algorithm released under the BSD-3-clause licence
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import Stream from "../lib/Stream.mjs";
|
||||
import {runHash} from "../lib/Hash.mjs";
|
||||
|
||||
/**
|
||||
* HASSH Server Fingerprint operation
|
||||
*/
|
||||
class HASSHServerFingerprint extends Operation {
|
||||
|
||||
/**
|
||||
* HASSHServerFingerprint constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "HASSH Server Fingerprint";
|
||||
this.module = "Crypto";
|
||||
this.description = "Generates a HASSH fingerprint to help identify SSH servers based on hashing together values from the Server Key Exchange Init message.<br><br>Input: A hex stream of the SSH_MSG_KEXINIT packet application layer from Server to Client.";
|
||||
this.infoURL = "https://engineering.salesforce.com/open-sourcing-hassh-abed3ae5044c";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Input format",
|
||||
type: "option",
|
||||
value: ["Hex", "Base64", "Raw"]
|
||||
},
|
||||
{
|
||||
name: "Output format",
|
||||
type: "option",
|
||||
value: ["Hash digest", "HASSH algorithms string", "Full details"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [inputFormat, outputFormat] = args;
|
||||
|
||||
input = Utils.convertToByteArray(input, inputFormat);
|
||||
const s = new Stream(new Uint8Array(input));
|
||||
|
||||
// Length
|
||||
const length = s.readInt(4);
|
||||
if (s.length !== length + 4)
|
||||
throw new OperationError("Incorrect packet length.");
|
||||
|
||||
// Padding length
|
||||
const paddingLength = s.readInt(1);
|
||||
|
||||
// Message code
|
||||
const messageCode = s.readInt(1);
|
||||
if (messageCode !== 20)
|
||||
throw new OperationError("Not a Key Exchange Init.");
|
||||
|
||||
// Cookie
|
||||
s.moveForwardsBy(16);
|
||||
|
||||
// KEX Algorithms
|
||||
const kexAlgosLength = s.readInt(4);
|
||||
const kexAlgos = s.readString(kexAlgosLength);
|
||||
|
||||
// Server Host Key Algorithms
|
||||
const serverHostKeyAlgosLength = s.readInt(4);
|
||||
s.moveForwardsBy(serverHostKeyAlgosLength);
|
||||
|
||||
// Encryption Algorithms Client to Server
|
||||
const encAlgosC2SLength = s.readInt(4);
|
||||
s.moveForwardsBy(encAlgosC2SLength);
|
||||
|
||||
// Encryption Algorithms Server to Client
|
||||
const encAlgosS2CLength = s.readInt(4);
|
||||
const encAlgosS2C = s.readString(encAlgosS2CLength);
|
||||
|
||||
// MAC Algorithms Client to Server
|
||||
const macAlgosC2SLength = s.readInt(4);
|
||||
s.moveForwardsBy(macAlgosC2SLength);
|
||||
|
||||
// MAC Algorithms Server to Client
|
||||
const macAlgosS2CLength = s.readInt(4);
|
||||
const macAlgosS2C = s.readString(macAlgosS2CLength);
|
||||
|
||||
// Compression Algorithms Client to Server
|
||||
const compAlgosC2SLength = s.readInt(4);
|
||||
s.moveForwardsBy(compAlgosC2SLength);
|
||||
|
||||
// Compression Algorithms Server to Client
|
||||
const compAlgosS2CLength = s.readInt(4);
|
||||
const compAlgosS2C = s.readString(compAlgosS2CLength);
|
||||
|
||||
// Languages Client to Server
|
||||
const langsC2SLength = s.readInt(4);
|
||||
s.moveForwardsBy(langsC2SLength);
|
||||
|
||||
// Languages Server to Client
|
||||
const langsS2CLength = s.readInt(4);
|
||||
s.moveForwardsBy(langsS2CLength);
|
||||
|
||||
// First KEX packet follows
|
||||
s.moveForwardsBy(1);
|
||||
|
||||
// Reserved
|
||||
s.moveForwardsBy(4);
|
||||
|
||||
// Padding string
|
||||
s.moveForwardsBy(paddingLength);
|
||||
|
||||
// Output
|
||||
const hassh = [
|
||||
kexAlgos,
|
||||
encAlgosS2C,
|
||||
macAlgosS2C,
|
||||
compAlgosS2C
|
||||
];
|
||||
const hasshStr = hassh.join(";");
|
||||
const hasshHash = runHash("md5", Utils.strToArrayBuffer(hasshStr));
|
||||
|
||||
switch (outputFormat) {
|
||||
case "HASSH algorithms string":
|
||||
return hasshStr;
|
||||
case "Full details":
|
||||
return `Hash digest:
|
||||
${hasshHash}
|
||||
|
||||
Full HASSH algorithms string:
|
||||
${hasshStr}
|
||||
|
||||
Key Exchange Algorithms:
|
||||
${kexAlgos}
|
||||
Encryption Algorithms Server to Client:
|
||||
${encAlgosS2C}
|
||||
MAC Algorithms Server to Client:
|
||||
${macAlgosS2C}
|
||||
Compression Algorithms Server to Client:
|
||||
${compAlgosS2C}`;
|
||||
case "Hash digest":
|
||||
default:
|
||||
return hasshHash;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default HASSHServerFingerprint;
|
||||
@@ -2,6 +2,13 @@
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2021
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* JA3 created by Salesforce
|
||||
* John B. Althouse
|
||||
* Jeff Atkinson
|
||||
* Josh Atkins
|
||||
*
|
||||
* Algorithm released under the BSD-3-clause licence
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
@@ -11,19 +18,19 @@ import Stream from "../lib/Stream.mjs";
|
||||
import {runHash} from "../lib/Hash.mjs";
|
||||
|
||||
/**
|
||||
* TLS JA3 Fingerprint operation
|
||||
* JA3 Fingerprint operation
|
||||
*/
|
||||
class TLSJA3Fingerprint extends Operation {
|
||||
class JA3Fingerprint extends Operation {
|
||||
|
||||
/**
|
||||
* TLSJA3Fingerprint constructor
|
||||
* JA3Fingerprint constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "TLS JA3 Fingerprint";
|
||||
this.name = "JA3 Fingerprint";
|
||||
this.module = "Crypto";
|
||||
this.description = "Generates a JA3 fingerprint to help identify TLS clients based on hashing together values from the Client Hello.<br><br>Input: A hex stream of the TLS Client Hello application layer.";
|
||||
this.description = "Generates a JA3 fingerprint to help identify TLS clients based on hashing together values from the Client Hello.<br><br>Input: A hex stream of the TLS Client Hello packet application layer.";
|
||||
this.infoURL = "https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
@@ -195,4 +202,4 @@ const GREASE_CIPHERSUITES = [
|
||||
0xfafa
|
||||
];
|
||||
|
||||
export default TLSJA3Fingerprint;
|
||||
export default JA3Fingerprint;
|
||||
145
src/core/operations/JA3SFingerprint.mjs
Normal file
145
src/core/operations/JA3SFingerprint.mjs
Normal file
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2021
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* JA3S created by Salesforce
|
||||
* John B. Althouse
|
||||
* Jeff Atkinson
|
||||
* Josh Atkins
|
||||
*
|
||||
* Algorithm released under the BSD-3-clause licence
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import Stream from "../lib/Stream.mjs";
|
||||
import {runHash} from "../lib/Hash.mjs";
|
||||
|
||||
/**
|
||||
* JA3S Fingerprint operation
|
||||
*/
|
||||
class JA3SFingerprint extends Operation {
|
||||
|
||||
/**
|
||||
* JA3SFingerprint constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "JA3S Fingerprint";
|
||||
this.module = "Crypto";
|
||||
this.description = "Generates a JA3S fingerprint to help identify TLS servers based on hashing together values from the Server Hello.<br><br>Input: A hex stream of the TLS Server Hello record application layer.";
|
||||
this.infoURL = "https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Input format",
|
||||
type: "option",
|
||||
value: ["Hex", "Base64", "Raw"]
|
||||
},
|
||||
{
|
||||
name: "Output format",
|
||||
type: "option",
|
||||
value: ["Hash digest", "JA3S string", "Full details"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [inputFormat, outputFormat] = args;
|
||||
|
||||
input = Utils.convertToByteArray(input, inputFormat);
|
||||
const s = new Stream(new Uint8Array(input));
|
||||
|
||||
const handshake = s.readInt(1);
|
||||
if (handshake !== 0x16)
|
||||
throw new OperationError("Not handshake data.");
|
||||
|
||||
// Version
|
||||
s.moveForwardsBy(2);
|
||||
|
||||
// Length
|
||||
const length = s.readInt(2);
|
||||
if (s.length !== length + 5)
|
||||
throw new OperationError("Incorrect handshake length.");
|
||||
|
||||
// Handshake type
|
||||
const handshakeType = s.readInt(1);
|
||||
if (handshakeType !== 2)
|
||||
throw new OperationError("Not a Server Hello.");
|
||||
|
||||
// Handshake length
|
||||
const handshakeLength = s.readInt(3);
|
||||
if (s.length !== handshakeLength + 9)
|
||||
throw new OperationError("Not enough data in Server Hello.");
|
||||
|
||||
// Hello version
|
||||
const helloVersion = s.readInt(2);
|
||||
|
||||
// Random
|
||||
s.moveForwardsBy(32);
|
||||
|
||||
// Session ID
|
||||
const sessionIDLength = s.readInt(1);
|
||||
s.moveForwardsBy(sessionIDLength);
|
||||
|
||||
// Cipher suite
|
||||
const cipherSuite = s.readInt(2);
|
||||
|
||||
// Compression Method
|
||||
s.moveForwardsBy(1);
|
||||
|
||||
// Extensions
|
||||
const extensionsLength = s.readInt(2);
|
||||
const extensions = s.getBytes(extensionsLength);
|
||||
const es = new Stream(extensions);
|
||||
const exts = [];
|
||||
while (es.hasMore()) {
|
||||
const type = es.readInt(2);
|
||||
const length = es.readInt(2);
|
||||
es.moveForwardsBy(length);
|
||||
exts.push(type);
|
||||
}
|
||||
|
||||
// Output
|
||||
const ja3s = [
|
||||
helloVersion.toString(),
|
||||
cipherSuite,
|
||||
exts.join("-")
|
||||
];
|
||||
const ja3sStr = ja3s.join(",");
|
||||
const ja3sHash = runHash("md5", Utils.strToArrayBuffer(ja3sStr));
|
||||
|
||||
switch (outputFormat) {
|
||||
case "JA3S string":
|
||||
return ja3sStr;
|
||||
case "Full details":
|
||||
return `Hash digest:
|
||||
${ja3sHash}
|
||||
|
||||
Full JA3S string:
|
||||
${ja3sStr}
|
||||
|
||||
TLS Version:
|
||||
${helloVersion.toString()}
|
||||
Cipher Suite:
|
||||
${cipherSuite}
|
||||
Extensions:
|
||||
${exts.join("-")}`;
|
||||
case "Hash digest":
|
||||
default:
|
||||
return ja3sHash;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default JA3SFingerprint;
|
||||
@@ -4,7 +4,7 @@
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import jpath from "jsonpath";
|
||||
import {JSONPath} from "jsonpath-plus";
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
@@ -27,14 +27,20 @@ class JPathExpression extends Operation {
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Query",
|
||||
"type": "string",
|
||||
"value": ""
|
||||
name: "Query",
|
||||
type: "string",
|
||||
value: ""
|
||||
},
|
||||
{
|
||||
"name": "Result delimiter",
|
||||
"type": "binaryShortString",
|
||||
"value": "\\n"
|
||||
name: "Result delimiter",
|
||||
type: "binaryShortString",
|
||||
value: "\\n"
|
||||
},
|
||||
{
|
||||
name: "Prevent eval",
|
||||
type: "boolean",
|
||||
value: true,
|
||||
description: "Evaluated expressions are disabled by default for security reasons"
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -45,18 +51,21 @@ class JPathExpression extends Operation {
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [query, delimiter] = args;
|
||||
let results,
|
||||
obj;
|
||||
const [query, delimiter, preventEval] = args;
|
||||
let results, jsonObj;
|
||||
|
||||
try {
|
||||
obj = JSON.parse(input);
|
||||
jsonObj = JSON.parse(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Invalid input JSON: ${err.message}`);
|
||||
}
|
||||
|
||||
try {
|
||||
results = jpath.query(obj, query);
|
||||
results = JSONPath({
|
||||
path: query,
|
||||
json: jsonObj,
|
||||
preventEval: preventEval
|
||||
});
|
||||
} catch (err) {
|
||||
throw new OperationError(`Invalid JPath expression: ${err.message}`);
|
||||
}
|
||||
|
||||
@@ -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(/"/g, "\\"");
|
||||
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;
|
||||
|
||||
@@ -114,8 +114,11 @@ class JSONToCSV extends Operation {
|
||||
* @returns {string}
|
||||
*/
|
||||
escapeCellContents(data, force=false) {
|
||||
if (typeof data === "number") data = data.toString();
|
||||
if (force && typeof data !== "string") data = JSON.stringify(data);
|
||||
if (data !== "string") {
|
||||
const isPrimitive = data == null || typeof data !== "object";
|
||||
if (isPrimitive) data = `${data}`;
|
||||
else if (force) data = JSON.stringify(data);
|
||||
}
|
||||
|
||||
// Double quotes should be doubled up
|
||||
data = data.replace(/"/g, '""');
|
||||
|
||||
@@ -26,6 +26,13 @@ class JWTDecode extends Operation {
|
||||
this.inputType = "string";
|
||||
this.outputType = "JSON";
|
||||
this.args = [];
|
||||
this.checks = [
|
||||
{
|
||||
pattern: "^ey([A-Za-z0-9_-]+)\\.ey([A-Za-z0-9_-]+)\\.([A-Za-z0-9_-]+)$",
|
||||
flags: "",
|
||||
args: []
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -52,6 +52,7 @@ class Jump extends Operation {
|
||||
const jmpIndex = getLabelIndex(label, state);
|
||||
|
||||
if (state.numJumps >= maxJumps || jmpIndex === -1) {
|
||||
state.numJumps = 0;
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
41
src/core/operations/LMHash.mjs
Normal file
41
src/core/operations/LMHash.mjs
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import {smbhash} from "ntlm";
|
||||
|
||||
/**
|
||||
* LM Hash operation
|
||||
*/
|
||||
class LMHash extends Operation {
|
||||
|
||||
/**
|
||||
* LMHash constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "LM Hash";
|
||||
this.module = "Crypto";
|
||||
this.description = "An LM Hash, or LAN Manager Hash, is a deprecated way of storing passwords on old Microsoft operating systems. It is particularly weak and can be cracked in seconds on modern hardware using rainbow tables.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/LAN_Manager#Password_hashing_algorithm";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
return smbhash.lmhash(input);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default LMHash;
|
||||
57
src/core/operations/LS47Decrypt.mjs
Normal file
57
src/core/operations/LS47Decrypt.mjs
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* @author n1073645 [n1073645@gmail.com]
|
||||
* @copyright Crown Copyright 2020
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import * as LS47 from "../lib/LS47.mjs";
|
||||
|
||||
/**
|
||||
* LS47 Decrypt operation
|
||||
*/
|
||||
class LS47Decrypt extends Operation {
|
||||
|
||||
/**
|
||||
* LS47Decrypt constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "LS47 Decrypt";
|
||||
this.module = "Crypto";
|
||||
this.description = "This is a slight improvement of the ElsieFour cipher as described by Alan Kaminsky. We use 7x7 characters instead of original (barely fitting) 6x6, to be able to encrypt some structured information. We also describe a simple key-expansion algorithm, because remembering passwords is popular. Similar security considerations as with ElsieFour hold.<br>The LS47 alphabet consists of following characters: <code>_abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()</code><br>An LS47 key is a permutation of the alphabet that is then represented in a 7x7 grid used for the encryption or decryption.";
|
||||
this.infoURL = "https://github.com/exaexa/ls47";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Password",
|
||||
type: "string",
|
||||
value: ""
|
||||
},
|
||||
{
|
||||
name: "Padding",
|
||||
type: "number",
|
||||
value: 10
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
this.paddingSize = parseInt(args[1], 10);
|
||||
|
||||
LS47.initTiles();
|
||||
|
||||
const key = LS47.deriveKey(args[0]);
|
||||
return LS47.decryptPad(key, input, this.paddingSize);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default LS47Decrypt;
|
||||
62
src/core/operations/LS47Encrypt.mjs
Normal file
62
src/core/operations/LS47Encrypt.mjs
Normal file
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* @author n1073645 [n1073645@gmail.com]
|
||||
* @copyright Crown Copyright 2020
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import * as LS47 from "../lib/LS47.mjs";
|
||||
|
||||
/**
|
||||
* LS47 Encrypt operation
|
||||
*/
|
||||
class LS47Encrypt extends Operation {
|
||||
|
||||
/**
|
||||
* LS47Encrypt constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "LS47 Encrypt";
|
||||
this.module = "Crypto";
|
||||
this.description = "This is a slight improvement of the ElsieFour cipher as described by Alan Kaminsky. We use 7x7 characters instead of original (barely fitting) 6x6, to be able to encrypt some structured information. We also describe a simple key-expansion algorithm, because remembering passwords is popular. Similar security considerations as with ElsieFour hold.<br>The LS47 alphabet consists of following characters: <code>_abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()</code><br>A LS47 key is a permutation of the alphabet that is then represented in a 7x7 grid used for the encryption or decryption.";
|
||||
this.infoURL = "https://github.com/exaexa/ls47";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Password",
|
||||
type: "string",
|
||||
value: ""
|
||||
},
|
||||
{
|
||||
name: "Padding",
|
||||
type: "number",
|
||||
value: 10
|
||||
},
|
||||
{
|
||||
name: "Signature",
|
||||
type: "string",
|
||||
value: ""
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
this.paddingSize = parseInt(args[1], 10);
|
||||
|
||||
LS47.initTiles();
|
||||
|
||||
const key = LS47.deriveKey(args[0]);
|
||||
return LS47.encryptPad(key, input, args[2], this.paddingSize);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default LS47Encrypt;
|
||||
43
src/core/operations/LZ4Compress.mjs
Normal file
43
src/core/operations/LZ4Compress.mjs
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import lz4 from "lz4js";
|
||||
|
||||
/**
|
||||
* LZ4 Compress operation
|
||||
*/
|
||||
class LZ4Compress extends Operation {
|
||||
|
||||
/**
|
||||
* LZ4Compress constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "LZ4 Compress";
|
||||
this.module = "Compression";
|
||||
this.description = "LZ4 is a lossless data compression algorithm that is focused on compression and decompression speed. It belongs to the LZ77 family of byte-oriented compression schemes.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/LZ4_(compression_algorithm)";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "ArrayBuffer";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
run(input, args) {
|
||||
const inBuf = new Uint8Array(input);
|
||||
const compressed = lz4.compress(inBuf);
|
||||
return compressed.buffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default LZ4Compress;
|
||||
43
src/core/operations/LZ4Decompress.mjs
Normal file
43
src/core/operations/LZ4Decompress.mjs
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import lz4 from "lz4js";
|
||||
|
||||
/**
|
||||
* LZ4 Decompress operation
|
||||
*/
|
||||
class LZ4Decompress extends Operation {
|
||||
|
||||
/**
|
||||
* LZ4Decompress constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "LZ4 Decompress";
|
||||
this.module = "Compression";
|
||||
this.description = "LZ4 is a lossless data compression algorithm that is focused on compression and decompression speed. It belongs to the LZ77 family of byte-oriented compression schemes.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/LZ4_(compression_algorithm)";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "ArrayBuffer";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
run(input, args) {
|
||||
const inBuf = new Uint8Array(input);
|
||||
const decompressed = lz4.decompress(inBuf);
|
||||
return decompressed.buffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default LZ4Decompress;
|
||||
64
src/core/operations/LZMACompress.mjs
Normal file
64
src/core/operations/LZMACompress.mjs
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* @author Matt C [me@mitt.dev]
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
import { compress } from "@blu3r4y/lzma";
|
||||
import {isWorkerEnvironment} from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* LZMA Compress operation
|
||||
*/
|
||||
class LZMACompress extends Operation {
|
||||
|
||||
/**
|
||||
* LZMACompress constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "LZMA Compress";
|
||||
this.module = "Compression";
|
||||
this.description = "Compresses data using the Lempel\u2013Ziv\u2013Markov chain algorithm. Compression mode determines the speed and effectiveness of the compression: 1 is fastest and less effective, 9 is slowest and most effective";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Markov_chain_algorithm";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "ArrayBuffer";
|
||||
this.args = [
|
||||
{
|
||||
name: "Compression Mode",
|
||||
type: "option",
|
||||
value: [
|
||||
"1", "2", "3", "4", "5", "6", "7", "8", "9"
|
||||
],
|
||||
"defaultIndex": 6
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const mode = Number(args[0]);
|
||||
return new Promise((resolve, reject) => {
|
||||
compress(new Uint8Array(input), mode, (result, error) => {
|
||||
if (error) {
|
||||
reject(new OperationError(`Failed to compress input: ${error.message}`));
|
||||
}
|
||||
// The compression returns as an Int8Array, but we can just get the unsigned data from the buffer
|
||||
resolve(new Int8Array(result).buffer);
|
||||
}, (percent) => {
|
||||
if (isWorkerEnvironment()) self.sendStatusMessage(`Compressing input: ${(percent*100).toFixed(2)}%`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default LZMACompress;
|
||||
57
src/core/operations/LZMADecompress.mjs
Normal file
57
src/core/operations/LZMADecompress.mjs
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* @author Matt C [me@mitt.dev]
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import {decompress} from "@blu3r4y/lzma";
|
||||
import Utils, {isWorkerEnvironment} from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* LZMA Decompress operation
|
||||
*/
|
||||
class LZMADecompress extends Operation {
|
||||
|
||||
/**
|
||||
* LZMADecompress constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "LZMA Decompress";
|
||||
this.module = "Compression";
|
||||
this.description = "Decompresses data using the Lempel-Ziv-Markov chain Algorithm.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Markov_chain_algorithm";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "ArrayBuffer";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
async run(input, args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
decompress(new Uint8Array(input), (result, error) => {
|
||||
if (error) {
|
||||
reject(new OperationError(`Failed to decompress input: ${error.message}`));
|
||||
}
|
||||
// The decompression returns either a String or an untyped unsigned int8 array, but we can just get the unsigned data from the buffer
|
||||
|
||||
if (typeof result == "string") {
|
||||
resolve(Utils.strToArrayBuffer(result));
|
||||
} else {
|
||||
resolve(new Int8Array(result).buffer);
|
||||
}
|
||||
}, (percent) => {
|
||||
if (isWorkerEnvironment()) self.sendStatusMessage(`Decompressing input: ${(percent*100).toFixed(2)}%`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default LZMADecompress;
|
||||
55
src/core/operations/LZStringCompress.mjs
Normal file
55
src/core/operations/LZStringCompress.mjs
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* @author crespyl [peter@crespyl.net]
|
||||
* @copyright Peter Jacobs 2021
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
import {COMPRESSION_OUTPUT_FORMATS, COMPRESSION_FUNCTIONS} from "../lib/LZString.mjs";
|
||||
|
||||
/**
|
||||
* LZString Compress operation
|
||||
*/
|
||||
class LZStringCompress extends Operation {
|
||||
|
||||
/**
|
||||
* LZStringCompress constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "LZString Compress";
|
||||
this.module = "Compression";
|
||||
this.description = "Compress the input with lz-string.";
|
||||
this.infoURL = "https://pieroxy.net/blog/pages/lz-string/index.html";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Compression Format",
|
||||
type: "option",
|
||||
defaultIndex: 0,
|
||||
value: COMPRESSION_OUTPUT_FORMATS
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const compress = COMPRESSION_FUNCTIONS[args[0]];
|
||||
if (compress) {
|
||||
return compress(input);
|
||||
} else {
|
||||
throw new OperationError("Unable to find compression function");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default LZStringCompress;
|
||||
56
src/core/operations/LZStringDecompress.mjs
Normal file
56
src/core/operations/LZStringDecompress.mjs
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* @author crespyl [peter@crespyl.net]
|
||||
* @copyright Peter Jacobs 2021
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
import {COMPRESSION_OUTPUT_FORMATS, DECOMPRESSION_FUNCTIONS} from "../lib/LZString.mjs";
|
||||
|
||||
/**
|
||||
* LZString Decompress operation
|
||||
*/
|
||||
class LZStringDecompress extends Operation {
|
||||
|
||||
/**
|
||||
* LZStringDecompress constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "LZString Decompress";
|
||||
this.module = "Compression";
|
||||
this.description = "Decompresses data that was compressed with lz-string.";
|
||||
this.infoURL = "https://pieroxy.net/blog/pages/lz-string/index.html";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Compression Format",
|
||||
type: "option",
|
||||
defaultIndex: 0,
|
||||
value: COMPRESSION_OUTPUT_FORMATS
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const decompress = DECOMPRESSION_FUNCTIONS[args[0]];
|
||||
if (decompress) {
|
||||
return decompress(input);
|
||||
} else {
|
||||
throw new OperationError("Unable to find decompression function");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default LZStringDecompress;
|
||||
@@ -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
|
||||
|
||||
@@ -20,10 +20,16 @@ class Merge extends Operation {
|
||||
this.name = "Merge";
|
||||
this.flowControl = true;
|
||||
this.module = "Default";
|
||||
this.description = "Consolidate all branches back into a single trunk. The opposite of Fork.";
|
||||
this.description = "Consolidate all branches back into a single trunk. The opposite of Fork. Unticking the Merge All checkbox will only consolidate all branches up to the nearest Fork/Subsection.";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
this.args = [
|
||||
{
|
||||
name: "Merge All",
|
||||
type: "boolean",
|
||||
value: true,
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
46
src/core/operations/NTHash.mjs
Normal file
46
src/core/operations/NTHash.mjs
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* @author brun0ne [brunonblok@gmail.com]
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
|
||||
import cptable from "codepage";
|
||||
import {runHash} from "../lib/Hash.mjs";
|
||||
|
||||
/**
|
||||
* NT Hash operation
|
||||
*/
|
||||
class NTHash extends Operation {
|
||||
|
||||
/**
|
||||
* NTHash constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "NT Hash";
|
||||
this.module = "Crypto";
|
||||
this.description = "An NT Hash, sometimes referred to as an NTLM hash, is a method of storing passwords on Windows systems. It works by running MD4 on UTF-16LE encoded input. NTLM hashes are considered weak because they can be brute-forced very easily with modern hardware.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/NT_LAN_Manager";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const format = 1200; // UTF-16LE
|
||||
const encoded = cptable.utils.encode(format, input);
|
||||
const hashed = runHash("md4", encoded);
|
||||
|
||||
return hashed.toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
export default NTHash;
|
||||
@@ -12,10 +12,8 @@ import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
|
||||
import Tesseract from "tesseract.js";
|
||||
const { createWorker } = Tesseract;
|
||||
|
||||
import process from "process";
|
||||
import { createWorker } from "tesseract.js";
|
||||
|
||||
/**
|
||||
* Optical Character Recognition operation
|
||||
@@ -55,7 +53,7 @@ class OpticalCharacterRecognition extends Operation {
|
||||
|
||||
const type = isImage(input);
|
||||
if (!type) {
|
||||
throw new OperationError("Invalid File Type");
|
||||
throw new OperationError("Unsupported file type (supported: jpg,png,pbm,bmp) or no file provided");
|
||||
}
|
||||
|
||||
const assetDir = isWorkerEnvironment() ? `${self.docURL}/assets/` : `${process.cwd()}/src/core/vendor/`;
|
||||
@@ -74,7 +72,7 @@ class OpticalCharacterRecognition extends Operation {
|
||||
}
|
||||
});
|
||||
await worker.load();
|
||||
self.sendStatusMessage("Loading English language...");
|
||||
self.sendStatusMessage(`Loading English language pack...`);
|
||||
await worker.loadLanguage("eng");
|
||||
self.sendStatusMessage("Intialising Tesseract API...");
|
||||
await worker.initialize("eng");
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
133
src/core/operations/PLISTViewer.mjs
Normal file
133
src/core/operations/PLISTViewer.mjs
Normal file
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* @author n1073645 [n1073645@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
|
||||
/**
|
||||
* P-list Viewer operation
|
||||
*/
|
||||
class PlistViewer extends Operation {
|
||||
|
||||
/**
|
||||
* PlistViewer constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "P-list Viewer";
|
||||
this.module = "Default";
|
||||
this.description = "In the macOS, iOS, NeXTSTEP, and GNUstep programming frameworks, property list files are files that store serialized objects. Property list files use the filename extension .plist, and thus are often referred to as p-list files.<br><br>This operation displays plist files in a human readable format.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Property_list";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
|
||||
// Regexes are designed to transform the xml format into a more readable string format.
|
||||
input = input.slice(input.indexOf("<plist"))
|
||||
.replace(/<plist.+>/g, "plist => ")
|
||||
.replace(/<dict>/g, "{")
|
||||
.replace(/<\/dict>/g, "}")
|
||||
.replace(/<array>/g, "[")
|
||||
.replace(/<\/array>/g, "]")
|
||||
.replace(/<key>.+<\/key>/g, m => `${m.slice(5, m.indexOf(/<\/key>/g)-5)}\t=> `)
|
||||
.replace(/<real>.+<\/real>/g, m => `${m.slice(6, m.indexOf(/<\/real>/g)-6)}\n`)
|
||||
.replace(/<string>.+<\/string>/g, m => `"${m.slice(8, m.indexOf(/<\/string>/g)-8)}"\n`)
|
||||
.replace(/<integer>.+<\/integer>/g, m => `${m.slice(9, m.indexOf(/<\/integer>/g)-9)}\n`)
|
||||
.replace(/<false\/>/g, m => "false")
|
||||
.replace(/<true\/>/g, m => "true")
|
||||
.replace(/<\/plist>/g, "/plist")
|
||||
.replace(/<date>.+<\/date>/g, m => `${m.slice(6, m.indexOf(/<\/integer>/g)-6)}`)
|
||||
.replace(/<data>(\s|.)+?<\/data>/g, m => `${m.slice(6, m.indexOf(/<\/data>/g)-6)}`)
|
||||
.replace(/[ \t\r\f\v]/g, "");
|
||||
|
||||
/**
|
||||
* Depending on the type of brace, it will increment the depth and amount of arrays accordingly.
|
||||
*
|
||||
* @param {string} elem
|
||||
* @param {array} vals
|
||||
* @param {number} offset
|
||||
*/
|
||||
function braces(elem, vals, offset) {
|
||||
const temp = vals.indexOf(elem);
|
||||
if (temp !== -1) {
|
||||
depthCount += offset;
|
||||
if (temp === 1)
|
||||
arrCount += offset;
|
||||
}
|
||||
}
|
||||
|
||||
let result = "";
|
||||
let arrCount = 0;
|
||||
let depthCount = 0;
|
||||
|
||||
/**
|
||||
* Formats the input after the regex has replaced all of the relevant parts.
|
||||
*
|
||||
* @param {array} input
|
||||
* @param {number} index
|
||||
*/
|
||||
function printIt(input, index) {
|
||||
if (!(input.length))
|
||||
return;
|
||||
|
||||
let temp = "";
|
||||
const origArr = arrCount;
|
||||
let currElem = input[0];
|
||||
|
||||
// If the current position points at a larger dynamic structure.
|
||||
if (currElem.indexOf("=>") !== -1) {
|
||||
|
||||
// If the LHS also points at a larger structure (nested plists in a dictionary).
|
||||
if (input[1].indexOf("=>") !== -1)
|
||||
temp = currElem.slice(0, -2) + " => " + input[1].slice(0, -2) + " =>\n";
|
||||
else
|
||||
temp = currElem.slice(0, -2) + " => " + input[1] + "\n";
|
||||
|
||||
input = input.slice(1);
|
||||
} else {
|
||||
// Controls the tab depth for how many closing braces there have been.
|
||||
|
||||
braces(currElem, ["}", "]"], -1);
|
||||
|
||||
// Has to be here since the formatting breaks otherwise.
|
||||
temp = currElem + "\n";
|
||||
}
|
||||
|
||||
currElem = input[0];
|
||||
|
||||
// Tab out to the correct distance.
|
||||
result += ("\t".repeat(depthCount));
|
||||
|
||||
// If it is enclosed in an array show index.
|
||||
if (arrCount > 0 && currElem !== "]")
|
||||
result += index.toString() + " => ";
|
||||
|
||||
result += temp;
|
||||
|
||||
// Controls the tab depth for how many opening braces there have been.
|
||||
braces(currElem, ["{", "["], 1);
|
||||
|
||||
// If there has been a new array then reset index.
|
||||
if (arrCount > origArr)
|
||||
return printIt(input.slice(1), 0);
|
||||
return printIt(input.slice(1), ++index);
|
||||
}
|
||||
|
||||
input = input.split("\n").filter(e => e !== "");
|
||||
printIt(input, 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export default PlistViewer;
|
||||
@@ -45,8 +45,8 @@ class ParseASN1HexString extends Operation {
|
||||
*/
|
||||
run(input, args) {
|
||||
const [index, truncateLen] = args;
|
||||
return r.ASN1HEX.dump(input.replace(/\s/g, ""), {
|
||||
"ommitLongOctet": truncateLen
|
||||
return r.ASN1HEX.dump(input.replace(/\s/g, "").toLowerCase(), {
|
||||
"ommit_long_octet": truncateLen
|
||||
}, index);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ class ParseSSHHostKey extends Operation {
|
||||
|
||||
this.name = "Parse SSH Host Key";
|
||||
this.module = "Default";
|
||||
this.description = "Parses a SSH host key and extracts fields from it.<br>The key type can be:<ul><li>ssh-rsa</li><li>ssh-dss</li><li>ecdsa-sha2</li></ul>The key format can be either Hex or Base64.";
|
||||
this.description = "Parses a SSH host key and extracts fields from it.<br>The key type can be:<ul><li>ssh-rsa</li><li>ssh-dss</li><li>ecdsa-sha2</li><li>ssh-ed25519</li></ul>The key format can be either Hex or Base64.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Secure_Shell";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
@@ -71,6 +71,8 @@ class ParseSSHHostKey extends Operation {
|
||||
} else if (keyType.startsWith("ecdsa-sha2")) {
|
||||
output += `\nCurve: ${Utils.byteArrayToChars(fromHex(fields[1]))}`;
|
||||
output += `\nPoint: 0x${fields.slice(2)}`;
|
||||
} else if (keyType === "ssh-ed25519") {
|
||||
output += `\nx: 0x${fields[1]}`;
|
||||
} else {
|
||||
output += "\nUnsupported key type.";
|
||||
output += `\nParameters: ${fields.slice(1)}`;
|
||||
|
||||
245
src/core/operations/ParseTCP.mjs
Normal file
245
src/core/operations/ParseTCP.mjs
Normal 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;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import r from "jsrsasign";
|
||||
import { fromBase64 } from "../lib/Base64.mjs";
|
||||
import { toHex } from "../lib/Hex.mjs";
|
||||
import { formatByteStr, formatDnStr } from "../lib/PublicKey.mjs";
|
||||
import { formatByteStr, formatDnObj } from "../lib/PublicKey.mjs";
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
@@ -57,27 +57,33 @@ class ParseX509Certificate extends Operation {
|
||||
const cert = new r.X509(),
|
||||
inputFormat = args[0];
|
||||
|
||||
switch (inputFormat) {
|
||||
case "DER Hex":
|
||||
input = input.replace(/\s/g, "");
|
||||
cert.readCertHex(input);
|
||||
break;
|
||||
case "PEM":
|
||||
cert.readCertPEM(input);
|
||||
break;
|
||||
case "Base64":
|
||||
cert.readCertHex(toHex(fromBase64(input, null, "byteArray"), ""));
|
||||
break;
|
||||
case "Raw":
|
||||
cert.readCertHex(toHex(Utils.strToByteArray(input), ""));
|
||||
break;
|
||||
default:
|
||||
throw "Undefined input format";
|
||||
let undefinedInputFormat = false;
|
||||
try {
|
||||
switch (inputFormat) {
|
||||
case "DER Hex":
|
||||
input = input.replace(/\s/g, "").toLowerCase();
|
||||
cert.readCertHex(input);
|
||||
break;
|
||||
case "PEM":
|
||||
cert.readCertPEM(input);
|
||||
break;
|
||||
case "Base64":
|
||||
cert.readCertHex(toHex(fromBase64(input, null, "byteArray"), ""));
|
||||
break;
|
||||
case "Raw":
|
||||
cert.readCertHex(toHex(Utils.strToByteArray(input), ""));
|
||||
break;
|
||||
default:
|
||||
undefinedInputFormat = true;
|
||||
}
|
||||
} catch (e) {
|
||||
throw "Certificate load error (non-certificate input?)";
|
||||
}
|
||||
if (undefinedInputFormat) throw "Undefined input format";
|
||||
|
||||
const sn = cert.getSerialNumberHex(),
|
||||
issuer = cert.getIssuerString(),
|
||||
subject = cert.getSubjectString(),
|
||||
issuer = cert.getIssuer(),
|
||||
subject = cert.getSubject(),
|
||||
pk = cert.getPublicKey(),
|
||||
pkFields = [],
|
||||
sig = cert.getSignatureValueHex();
|
||||
@@ -170,10 +176,10 @@ class ParseX509Certificate extends Operation {
|
||||
extensions = cert.getInfo().split("X509v3 Extensions:\n")[1].split("signature")[0];
|
||||
} catch (err) {}
|
||||
|
||||
const issuerStr = formatDnStr(issuer, 2),
|
||||
const issuerStr = formatDnObj(issuer, 2),
|
||||
nbDate = formatDate(cert.getNotBefore()),
|
||||
naDate = formatDate(cert.getNotAfter()),
|
||||
subjectStr = formatDnStr(subject, 2);
|
||||
subjectStr = formatDnObj(subject, 2);
|
||||
|
||||
return `Version: ${cert.version} (0x${Utils.hex(cert.version - 1)})
|
||||
Serial number: ${new r.BigInteger(sn, 16).toString()} (0x${sn})
|
||||
|
||||
@@ -20,12 +20,30 @@ class ProtobufDecode extends Operation {
|
||||
super();
|
||||
|
||||
this.name = "Protobuf Decode";
|
||||
this.module = "Default";
|
||||
this.description = "Decodes any Protobuf encoded data to a JSON representation of the data using the field number as the field key.";
|
||||
this.module = "Protobuf";
|
||||
this.description = "Decodes any Protobuf encoded data to a JSON representation of the data using the field number as the field key.<br><br>If a .proto schema is defined, the encoded data will be decoded with reference to the schema. Only one message instance will be decoded. <br><br><u>Show Unknown Fields</u><br>When a schema is used, this option shows fields that are present in the input data but not defined in the schema.<br><br><u>Show Types</u><br>Show the type of a field next to its name. For undefined fields, the wiretype and example types are shown instead.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Protocol_Buffers";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "JSON";
|
||||
this.args = [];
|
||||
this.args = [
|
||||
{
|
||||
name: "Schema (.proto text)",
|
||||
type: "text",
|
||||
value: "",
|
||||
rows: 8,
|
||||
hint: "Drag and drop is enabled on this ingredient"
|
||||
},
|
||||
{
|
||||
name: "Show Unknown Fields",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "Show Types",
|
||||
type: "boolean",
|
||||
value: false
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,7 +54,7 @@ class ProtobufDecode extends Operation {
|
||||
run(input, args) {
|
||||
input = new Uint8Array(input);
|
||||
try {
|
||||
return Protobuf.decode(input);
|
||||
return Protobuf.decode(input, args);
|
||||
} catch (err) {
|
||||
throw new OperationError(err);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user