mirror of
https://github.com/gchq/CyberChef
synced 2025-12-05 23:53:27 +00:00
Compare commits
265 Commits
v10.8.1
...
bug/disabl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3ee16f38b | ||
|
|
e1d3af26da | ||
|
|
9d014aeaa5 | ||
|
|
156de53dc1 | ||
|
|
7ecc235b31 | ||
|
|
51ed3b2fc1 | ||
|
|
306e29da76 | ||
|
|
9cc84b1c62 | ||
|
|
f02b3f22ad | ||
|
|
e9b8163626 | ||
|
|
20390ae08e | ||
|
|
fcdcce7ee4 | ||
|
|
3d017d5f84 | ||
|
|
5455061e15 | ||
|
|
f4995dbc30 | ||
|
|
cc7cc7f8fd | ||
|
|
6a92f922cb | ||
|
|
71c8c8aac0 | ||
|
|
304266020d | ||
|
|
3deb121043 | ||
|
|
1fcc365d9e | ||
|
|
3822c6c520 | ||
|
|
47c85a105d | ||
|
|
d3adfc7c3e | ||
|
|
270a333179 | ||
|
|
895a929925 | ||
|
|
748379f0b0 | ||
|
|
da74d9b22d | ||
|
|
1fde2fba29 | ||
|
|
a50d4d63eb | ||
|
|
dbc90090cf | ||
|
|
e65869a10b | ||
|
|
d635cca210 | ||
|
|
0e82e4b7c6 | ||
|
|
5f88ae44ec | ||
|
|
7a5225c961 | ||
|
|
a477f47aec | ||
|
|
965570d250 | ||
|
|
ab37c1e562 | ||
|
|
40fda00db4 | ||
|
|
d5374454f4 | ||
|
|
4c5577ddeb | ||
|
|
534ab23d9b | ||
|
|
fe9f4fa7a9 | ||
|
|
a8b1050d75 | ||
|
|
e80d3d59bc | ||
|
|
1efbd9dfd1 | ||
|
|
6c30c9c6b8 | ||
|
|
4528a1bdb6 | ||
|
|
c23a8de5a0 | ||
|
|
0cd4d41cdc | ||
|
|
2b275f0897 | ||
|
|
63913f4d45 | ||
|
|
b6c95492f1 | ||
|
|
ae03e34489 | ||
|
|
7eb887ca51 | ||
|
|
74d0166682 | ||
|
|
18159ce806 | ||
|
|
86d59783fa | ||
|
|
fb818c3149 | ||
|
|
37398188f9 | ||
|
|
d1a0da3f8d | ||
|
|
57c8c6dbc6 | ||
|
|
bbebba6481 | ||
|
|
f0a49fefa4 | ||
|
|
48f3bf9ea7 | ||
|
|
b7a7eebc78 | ||
|
|
2e76e44a5a | ||
|
|
718ce9ea11 | ||
|
|
a79be1e3ef | ||
|
|
0a709acafe | ||
|
|
29efd77eaf | ||
|
|
2d6ac8023e | ||
|
|
2f42f515b0 | ||
|
|
f304f0832b | ||
|
|
801f3a578d | ||
|
|
0a353eeb37 | ||
|
|
2e2490ce47 | ||
|
|
361a35b44c | ||
|
|
e61d64f618 | ||
|
|
42ad9a49f3 | ||
|
|
7538be68c5 | ||
|
|
21ac516248 | ||
|
|
7e7195c291 | ||
|
|
1fbc7e03f0 | ||
|
|
7b54d9e873 | ||
|
|
8ab2256b88 | ||
|
|
6e8c759dde | ||
|
|
a429902d41 | ||
|
|
f6c5a04088 | ||
|
|
edc23a860d | ||
|
|
67195f65e7 | ||
|
|
4619a511d4 | ||
|
|
cbf990fab9 | ||
|
|
8f182e4a9b | ||
|
|
7a2c9ddbc4 | ||
|
|
28e2a391b8 | ||
|
|
f86817bc86 | ||
|
|
cc28c6af1a | ||
|
|
d21a6c8598 | ||
|
|
7b2d572902 | ||
|
|
0cfb67bd06 | ||
|
|
f606d4b25f | ||
|
|
2191d20fb5 | ||
|
|
d13218caaf | ||
|
|
00f7914c5c | ||
|
|
a09f8451fd | ||
|
|
670c370b90 | ||
|
|
b2e400f474 | ||
|
|
8c283c7b19 | ||
|
|
db331e94ee | ||
|
|
2e284d3842 | ||
|
|
a81b2064d4 | ||
|
|
a23e47d8f9 | ||
|
|
e3033173d7 | ||
|
|
1fbf6c94cd | ||
|
|
bf9066ae2e | ||
|
|
33a473c09b | ||
|
|
409e795ce9 | ||
|
|
6ca60cb013 | ||
|
|
ef52195167 | ||
|
|
ed930d2364 | ||
|
|
1b870e559e | ||
|
|
d3fb8bd6e9 | ||
|
|
1adc2ff930 | ||
|
|
fc40580dce | ||
|
|
4652608297 | ||
|
|
0f0efefbf7 | ||
|
|
ab0493f53a | ||
|
|
a1892d4411 | ||
|
|
016825d4de | ||
|
|
c35557aea5 | ||
|
|
b5959c6f01 | ||
|
|
2000938040 | ||
|
|
c795271502 | ||
|
|
1d4c810554 | ||
|
|
ccd3839a9b | ||
|
|
fda77cf37a | ||
|
|
dc8c185c39 | ||
|
|
99efcb521d | ||
|
|
d2bd397e8c | ||
|
|
944810614a | ||
|
|
21e5641196 | ||
|
|
077b11e33b | ||
|
|
8d4ad6ae75 | ||
|
|
ab47b3557f | ||
|
|
c5e5ed2b4d | ||
|
|
dd2cfe8bac | ||
|
|
8a17abae45 | ||
|
|
dfedfa9f4c | ||
|
|
56f92afbf4 | ||
|
|
52709f0ecb | ||
|
|
df140b5098 | ||
|
|
6b95ba7dd6 | ||
|
|
61295a968e | ||
|
|
0717407bea | ||
|
|
c46660a0d9 | ||
|
|
4c6200f233 | ||
|
|
3983e1a8e2 | ||
|
|
a6b774da81 | ||
|
|
de8ed6962d | ||
|
|
98edef389c | ||
|
|
1b16c26699 | ||
|
|
866c9a94ae | ||
|
|
6677317e27 | ||
|
|
5c563c2bdf | ||
|
|
8647b50cca | ||
|
|
21dc5d9de0 | ||
|
|
e258e5a783 | ||
|
|
75a28b558e | ||
|
|
6efa2ddfa4 | ||
|
|
b88fbcc960 | ||
|
|
7ccf8cbacd | ||
|
|
a1f6960d4e | ||
|
|
2784978eb5 | ||
|
|
b4133a0afd | ||
|
|
d59ebdd0dc | ||
|
|
3b5225a94f | ||
|
|
acce7ca717 | ||
|
|
d29dbe78d3 | ||
|
|
4fdea84534 | ||
|
|
0f14d23599 | ||
|
|
877c83eae7 | ||
|
|
27b7e3c4d6 | ||
|
|
77b7d7ee0b | ||
|
|
6edf731d46 | ||
|
|
6fd00e2598 | ||
|
|
862cfdf0ae | ||
|
|
943d01c208 | ||
|
|
ef59634c15 | ||
|
|
674c8c7c87 | ||
|
|
953861ab30 | ||
|
|
0026d77b7b | ||
|
|
ee77e0a1e4 | ||
|
|
f1dcc339b3 | ||
|
|
1f316a2f32 | ||
|
|
a5f9a8726b | ||
|
|
64111b8b7b | ||
|
|
762cf3ca41 | ||
|
|
70ff3a52ca | ||
|
|
e4077fb63b | ||
|
|
65ffd8d65d | ||
|
|
16dfb3fac6 | ||
|
|
ef5ff5bec6 | ||
|
|
e1c73a64ad | ||
|
|
81e1abd682 | ||
|
|
9068b6c17a | ||
|
|
5992ba12f1 | ||
|
|
bf833a39fc | ||
|
|
fccc3584d8 | ||
|
|
963e2839ce | ||
|
|
e85acee509 | ||
|
|
4e9567f539 | ||
|
|
a9c00a5856 | ||
|
|
c4e7c41a6e | ||
|
|
210186e754 | ||
|
|
b4c14219b6 | ||
|
|
299a3c48a1 | ||
|
|
cd0aee7626 | ||
|
|
bc82f590d4 | ||
|
|
bebb216df2 | ||
|
|
6331c20306 | ||
|
|
4dc4c7edd2 | ||
|
|
d2ff03cea4 | ||
|
|
61d587a4a5 | ||
|
|
85da5f83b5 | ||
|
|
6c0c53d00f | ||
|
|
196bce04cc | ||
|
|
ba82941cef | ||
|
|
63449872da | ||
|
|
14ee3f0f4b | ||
|
|
774828823c | ||
|
|
9e73e2555b | ||
|
|
dc68b7d9bf | ||
|
|
7a38504015 | ||
|
|
26fa5f3d1d | ||
|
|
8b5b17b8e0 | ||
|
|
7cfb5e0b2a | ||
|
|
e973ea6f08 | ||
|
|
a942fe92fd | ||
|
|
b5e3a6c5a3 | ||
|
|
24cd4033c4 | ||
|
|
efda16b039 | ||
|
|
cb98672549 | ||
|
|
1dfb231033 | ||
|
|
4f0fa2a299 | ||
|
|
c14098a27c | ||
|
|
653af6a300 | ||
|
|
893b84d042 | ||
|
|
19423cc437 | ||
|
|
3ea12a2e1b | ||
|
|
5001adf221 | ||
|
|
cd4e70b24b | ||
|
|
ed7baf57f0 | ||
|
|
3bb6a40f82 | ||
|
|
6b76b7004a | ||
|
|
81605b2222 | ||
|
|
9e17825b53 | ||
|
|
c689cf7f13 | ||
|
|
3546ee30a2 | ||
|
|
794e0effba | ||
|
|
cd15a8c406 | ||
|
|
be2080259e | ||
|
|
55cac17456 | ||
|
|
846e84d3a4 |
41
.devcontainer/devcontainer.json
Normal file
41
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,41 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node
|
||||
{
|
||||
"name": "CyberChef",
|
||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||
"image": "mcr.microsoft.com/devcontainers/javascript-node:1-18-bookworm",
|
||||
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/github-cli": "latest"
|
||||
},
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
"forwardPorts": [8080],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": {
|
||||
"npm": "bash -c \"sudo chown node node_modules && npm install\""
|
||||
},
|
||||
|
||||
"containerEnv": {
|
||||
"DISPLAY": ":99"
|
||||
},
|
||||
|
||||
"mounts": [
|
||||
"source=${localWorkspaceFolderBasename}-node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume"
|
||||
],
|
||||
|
||||
// Configure tool-specific properties.
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"GitHub.vscode-github-actions"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||
// "remoteUser": "root"
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
src/core/vendor/**
|
||||
116
.eslintrc.json
116
.eslintrc.json
@@ -1,116 +0,0 @@
|
||||
{
|
||||
"parser": "@babel/eslint-parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2022,
|
||||
"ecmaFeatures": {
|
||||
"impliedStrict": true
|
||||
},
|
||||
"sourceType": "module",
|
||||
"allowImportExportEverywhere": true
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"rules": {
|
||||
// enable additional rules
|
||||
"no-eval": "error",
|
||||
"no-implied-eval": "error",
|
||||
"dot-notation": "error",
|
||||
"eqeqeq": ["error", "smart"],
|
||||
"no-caller": "error",
|
||||
"no-extra-bind": "error",
|
||||
"no-unused-expressions": "error",
|
||||
"no-useless-call": "error",
|
||||
"no-useless-return": "error",
|
||||
"radix": "warn",
|
||||
|
||||
// modify rules from base configurations
|
||||
"no-unused-vars": ["error", {
|
||||
"args": "none",
|
||||
"vars": "all"
|
||||
}],
|
||||
"no-empty": ["error", {
|
||||
"allowEmptyCatch": true
|
||||
}],
|
||||
|
||||
// disable rules from base configurations
|
||||
"no-control-regex": "off",
|
||||
"require-atomic-updates": "off",
|
||||
"no-async-promise-executor": "off",
|
||||
|
||||
// stylistic conventions
|
||||
"brace-style": ["error", "1tbs"],
|
||||
"space-before-blocks": ["error", "always"],
|
||||
"block-spacing": "error",
|
||||
"array-bracket-spacing": "error",
|
||||
"comma-spacing": "error",
|
||||
"spaced-comment": ["error", "always", { "exceptions": ["/"] } ],
|
||||
"comma-style": "error",
|
||||
"computed-property-spacing": "error",
|
||||
"no-trailing-spaces": "warn",
|
||||
"eol-last": "error",
|
||||
"func-call-spacing": "error",
|
||||
"key-spacing": ["warn", {
|
||||
"mode": "minimum"
|
||||
}],
|
||||
"indent": ["error", 4, {
|
||||
"ignoreComments": true,
|
||||
"ArrayExpression": "first",
|
||||
"SwitchCase": 1
|
||||
}],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"quotes": ["error", "double", {
|
||||
"avoidEscape": true,
|
||||
"allowTemplateLiterals": true
|
||||
}],
|
||||
"camelcase": ["error", {
|
||||
"properties": "always"
|
||||
}],
|
||||
"semi": ["error", "always"],
|
||||
"unicode-bom": "error",
|
||||
"require-jsdoc": ["error", {
|
||||
"require": {
|
||||
"FunctionDeclaration": true,
|
||||
"MethodDefinition": true,
|
||||
"ClassDeclaration": true,
|
||||
"ArrowFunctionExpression": true
|
||||
}
|
||||
}],
|
||||
"keyword-spacing": ["error", {
|
||||
"before": true,
|
||||
"after": true
|
||||
}],
|
||||
"no-multiple-empty-lines": ["warn", {
|
||||
"max": 2,
|
||||
"maxEOF": 1,
|
||||
"maxBOF": 0
|
||||
}],
|
||||
"no-whitespace-before-property": "error",
|
||||
"operator-linebreak": ["error", "after"],
|
||||
"space-in-parens": "error",
|
||||
"no-var": "error",
|
||||
"prefer-const": "error"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": "tests/**/*",
|
||||
"rules": {
|
||||
"no-unused-expressions": "off",
|
||||
"no-console": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globals": {
|
||||
"$": false,
|
||||
"jQuery": false,
|
||||
"log": false,
|
||||
"app": false,
|
||||
|
||||
"COMPILE_TIME": false,
|
||||
"COMPILE_MSG": false,
|
||||
"PKG_VERSION": false
|
||||
}
|
||||
}
|
||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
||||
1
.github/workflows/master.yml
vendored
1
.github/workflows/master.yml
vendored
@@ -19,6 +19,7 @@ jobs:
|
||||
|
||||
- name: Install
|
||||
run: |
|
||||
export DETECT_CHROMEDRIVER_VERSION=true
|
||||
npm install
|
||||
npm run setheapsize
|
||||
|
||||
|
||||
1
.github/workflows/pull_requests.yml
vendored
1
.github/workflows/pull_requests.yml
vendored
@@ -18,6 +18,7 @@ jobs:
|
||||
|
||||
- name: Install
|
||||
run: |
|
||||
export DETECT_CHROMEDRIVER_VERSION=true
|
||||
npm install
|
||||
npm run setheapsize
|
||||
|
||||
|
||||
103
CHANGELOG.md
103
CHANGELOG.md
@@ -13,6 +13,60 @@ All major and minor version changes will be documented in this file. Details of
|
||||
|
||||
## Details
|
||||
|
||||
### [10.19.0] - 2024-06-21
|
||||
- Add support for ECDSA and DSA in 'Parse CSR' [@robinsandhu] | [#1828]
|
||||
- Fix typos in SIGABA.mjs [@eltociear] | [#1834]
|
||||
|
||||
### [10.18.0] - 2024-04-24
|
||||
- Added 'XXTEA Encrypt' and 'XXTEA Decrypt' operations [@n1474335] | [0a353ee]
|
||||
|
||||
### [10.17.0] - 2024-04-13
|
||||
- Fix unit test 'expectOutput' implementation [@zb3] | [#1783]
|
||||
- Add accessibility labels for icons [@e218736] | [#1743]
|
||||
- Add focus styling for keyboard navigation [@e218736] | [#1739]
|
||||
- Add support for operation option hiding [@TheZ3ro] | [#541]
|
||||
- Improve efficiency of RAKE implementation [@sw5678] | [#1751]
|
||||
- Require (a, 26) to be coprime in 'Affine Encode' [@EvieHarv] | [#1788]
|
||||
- Added 'JWK to PEM' operation [@cplussharp] | [#1277]
|
||||
- Added 'PEM to JWK' operation [@cplussharp] | [#1277]
|
||||
- Added 'Public Key from Certificate' operation [@cplussharp] | [#1642]
|
||||
- Added 'Public Key from Private Key' operation [@cplussharp] | [#1642]
|
||||
|
||||
### [10.16.0] - 2024-04-12
|
||||
- Added 'JA4Server Fingerprint' operation [@n1474335] | [#1789]
|
||||
|
||||
### [10.15.0] - 2024-04-02
|
||||
- Fix Ciphersaber2 key concatenation [@zb3] | [#1765]
|
||||
- Fix DeriveEVPKey's array parsing [@zb3] | [#1767]
|
||||
- Fix JWT operations [@a3957273] | [#1769]
|
||||
- Added 'Parse Certificate Signing Request' operation [@jkataja] | [#1504]
|
||||
- Added 'Extract Hash Values' operation [@MShwed] | [#512]
|
||||
- Added 'DateTime Delta' operation [@tomgond] | [#1732]
|
||||
|
||||
### [10.14.0] - 2024-03-31
|
||||
- Added 'To Float' and 'From Float' operations [@tcode2k16] | [#1762]
|
||||
- Fix ChaCha raw export option [@joostrijneveld] | [#1606]
|
||||
- Update x86 disassembler vendor library [@evanreichard] | [#1197]
|
||||
- Allow variable Blowfish key sizes [@cbeuw] | [#933]
|
||||
- Added 'XXTEA' operation [@devcydo] | [#1361]
|
||||
|
||||
### [10.13.0] - 2024-03-30
|
||||
- Added 'FangURL' operation [@breakersall] [@arnydo] | [#1591] [#654]
|
||||
|
||||
### [10.12.0] - 2024-03-29
|
||||
- Added 'Salsa20' and 'XSalsa20' operation [@joostrijneveld] | [#1750]
|
||||
|
||||
### [10.11.0] - 2024-03-29
|
||||
- Add HEIC/HEIF file signatures [@simonw] | [#1757]
|
||||
- Update xmldom to fix medium security vulnerability [@chriswhite199] | [#1752]
|
||||
- Update JSONWebToken to fix medium security vulnerability [@chriswhite199] | [#1753]
|
||||
|
||||
### [10.10.0] - 2024-03-27
|
||||
- Added 'JA4 Fingerprint' operation [@n1474335] | [#1759]
|
||||
|
||||
### [10.9.0] - 2024-03-26
|
||||
- Line ending sequences and UTF-8 character encoding are now detected automatically [@n1474335] | [65ffd8d]
|
||||
|
||||
### [10.8.0] - 2024-02-13
|
||||
- Add official Docker images [@AshCorr] | [#1699]
|
||||
|
||||
@@ -386,6 +440,17 @@ All major and minor version changes will be documented in this file. Details of
|
||||
## [4.0.0] - 2016-11-28
|
||||
- Initial open source commit [@n1474335] | [b1d73a72](https://github.com/gchq/CyberChef/commit/b1d73a725dc7ab9fb7eb789296efd2b7e4b08306)
|
||||
|
||||
[10.19.0]: https://github.com/gchq/CyberChef/releases/tag/v10.19.0
|
||||
[10.18.0]: https://github.com/gchq/CyberChef/releases/tag/v10.18.0
|
||||
[10.17.0]: https://github.com/gchq/CyberChef/releases/tag/v10.17.0
|
||||
[10.16.0]: https://github.com/gchq/CyberChef/releases/tag/v10.16.0
|
||||
[10.15.0]: https://github.com/gchq/CyberChef/releases/tag/v10.15.0
|
||||
[10.14.0]: https://github.com/gchq/CyberChef/releases/tag/v10.14.0
|
||||
[10.13.0]: https://github.com/gchq/CyberChef/releases/tag/v10.13.0
|
||||
[10.12.0]: https://github.com/gchq/CyberChef/releases/tag/v10.12.0
|
||||
[10.11.0]: https://github.com/gchq/CyberChef/releases/tag/v10.11.0
|
||||
[10.10.0]: https://github.com/gchq/CyberChef/releases/tag/v10.10.0
|
||||
[10.9.0]: https://github.com/gchq/CyberChef/releases/tag/v10.9.0
|
||||
[10.8.0]: https://github.com/gchq/CyberChef/releases/tag/v10.7.0
|
||||
[10.7.0]: https://github.com/gchq/CyberChef/releases/tag/v10.7.0
|
||||
[10.6.0]: https://github.com/gchq/CyberChef/releases/tag/v10.6.0
|
||||
@@ -551,6 +616,20 @@ All major and minor version changes will be documented in this file. Details of
|
||||
[@sg5506844]: https://github.com/sg5506844
|
||||
[@AliceGrey]: https://github.com/AliceGrey
|
||||
[@AshCorr]: https://github.com/AshCorr
|
||||
[@simonw]: https://github.com/simonw
|
||||
[@chriswhite199]: https://github.com/chriswhite199
|
||||
[@breakersall]: https://github.com/breakersall
|
||||
[@evanreichard]: https://github.com/evanreichard
|
||||
[@devcydo]: https://github.com/devcydo
|
||||
[@zb3]: https://github.com/zb3
|
||||
[@jkataja]: https://github.com/jkataja
|
||||
[@tomgond]: https://github.com/tomgond
|
||||
[@e218736]: https://github.com/e218736
|
||||
[@TheZ3ro]: https://github.com/TheZ3ro
|
||||
[@EvieHarv]: https://github.com/EvieHarv
|
||||
[@cplussharp]: https://github.com/cplussharp
|
||||
[@robinsandhu]: https://github.com/robinsandhu
|
||||
[@eltociear]: https://github.com/eltociear
|
||||
|
||||
|
||||
[8ad18b]: https://github.com/gchq/CyberChef/commit/8ad18bc7db6d9ff184ba3518686293a7685bf7b7
|
||||
@@ -561,6 +640,8 @@ All major and minor version changes will be documented in this file. Details of
|
||||
[a895d1d]: https://github.com/gchq/CyberChef/commit/a895d1d82a2f92d440a0c5eca2bc7c898107b737
|
||||
[31a7f83]: https://github.com/gchq/CyberChef/commit/31a7f83b82e78927f89689f323fcb9185144d6ff
|
||||
[760eff4]: https://github.com/gchq/CyberChef/commit/760eff49b5307aaa3104c5e5b437ffe62299acd1
|
||||
[65ffd8d]: https://github.com/gchq/CyberChef/commit/65ffd8d65d88eb369f6f61a5d1d0f807179bffb7
|
||||
[0a353ee]: https://github.com/gchq/CyberChef/commit/0a353eeb378b9ca5d49e23c7dfc175ae07107b08
|
||||
|
||||
[#95]: https://github.com/gchq/CyberChef/pull/299
|
||||
[#173]: https://github.com/gchq/CyberChef/pull/173
|
||||
@@ -677,4 +758,24 @@ All major and minor version changes will be documented in this file. Details of
|
||||
[#1667]: https://github.com/gchq/CyberChef/issues/1667
|
||||
[#1555]: https://github.com/gchq/CyberChef/issues/1555
|
||||
[#1694]: https://github.com/gchq/CyberChef/issues/1694
|
||||
[#1699]: https://github.com/gchq/CyberChef/issues/1694
|
||||
[#1699]: https://github.com/gchq/CyberChef/issues/1699
|
||||
[#1757]: https://github.com/gchq/CyberChef/issues/1757
|
||||
[#1752]: https://github.com/gchq/CyberChef/issues/1752
|
||||
[#1753]: https://github.com/gchq/CyberChef/issues/1753
|
||||
[#1750]: https://github.com/gchq/CyberChef/issues/1750
|
||||
[#1591]: https://github.com/gchq/CyberChef/issues/1591
|
||||
[#654]: https://github.com/gchq/CyberChef/issues/654
|
||||
[#1762]: https://github.com/gchq/CyberChef/issues/1762
|
||||
[#1606]: https://github.com/gchq/CyberChef/issues/1606
|
||||
[#1197]: https://github.com/gchq/CyberChef/issues/1197
|
||||
[#933]: https://github.com/gchq/CyberChef/issues/933
|
||||
[#1361]: https://github.com/gchq/CyberChef/issues/1361
|
||||
[#1765]: https://github.com/gchq/CyberChef/issues/1765
|
||||
[#1767]: https://github.com/gchq/CyberChef/issues/1767
|
||||
[#1769]: https://github.com/gchq/CyberChef/issues/1769
|
||||
[#1759]: https://github.com/gchq/CyberChef/issues/1759
|
||||
[#1504]: https://github.com/gchq/CyberChef/issues/1504
|
||||
[#512]: https://github.com/gchq/CyberChef/issues/512
|
||||
[#1732]: https://github.com/gchq/CyberChef/issues/1732
|
||||
[#1789]: https://github.com/gchq/CyberChef/issues/1789
|
||||
|
||||
|
||||
18
Gruntfile.js
18
Gruntfile.js
@@ -86,10 +86,12 @@ module.exports = function (grunt) {
|
||||
|
||||
|
||||
// Project configuration
|
||||
const compileTime = grunt.template.today("UTC:dd/mm/yyyy HH:MM:ss") + " UTC",
|
||||
const compileYear = grunt.template.today("UTC:yyyy"),
|
||||
compileTime = grunt.template.today("UTC:dd/mm/yyyy HH:MM:ss") + " UTC",
|
||||
pkg = grunt.file.readJSON("package.json"),
|
||||
webpackConfig = require("./webpack.config.js"),
|
||||
BUILD_CONSTANTS = {
|
||||
COMPILE_YEAR: JSON.stringify(compileYear),
|
||||
COMPILE_TIME: JSON.stringify(compileTime),
|
||||
COMPILE_MSG: JSON.stringify(grunt.option("compile-msg") || grunt.option("msg") || ""),
|
||||
PKG_VERSION: JSON.stringify(pkg.version),
|
||||
@@ -125,6 +127,7 @@ module.exports = function (grunt) {
|
||||
filename: "index.html",
|
||||
template: "./src/web/html/index.html",
|
||||
chunks: ["main"],
|
||||
compileYear: compileYear,
|
||||
compileTime: compileTime,
|
||||
version: pkg.version,
|
||||
minify: {
|
||||
@@ -227,6 +230,7 @@ module.exports = function (grunt) {
|
||||
filename: "index.html",
|
||||
template: "./src/web/html/index.html",
|
||||
chunks: ["main"],
|
||||
compileYear: compileYear,
|
||||
compileTime: compileTime,
|
||||
version: pkg.version,
|
||||
})
|
||||
@@ -427,6 +431,18 @@ module.exports = function (grunt) {
|
||||
}
|
||||
},
|
||||
stdout: false
|
||||
},
|
||||
fixJimpModule: {
|
||||
command: function () {
|
||||
switch (process.platform) {
|
||||
case "darwin":
|
||||
// Space added before comma to prevent multiple modifications
|
||||
return `sed -i '' 's/"es\\/index.js",/"es\\/index.js" ,\\n "type": "module",/' ./node_modules/jimp/package.json`;
|
||||
default:
|
||||
return `sed -i 's/"es\\/index.js",/"es\\/index.js" ,\\n "type": "module",/' ./node_modules/jimp/package.json`;
|
||||
}
|
||||
},
|
||||
stdout: false
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
129
eslint.config.mjs
Executable file
129
eslint.config.mjs
Executable file
@@ -0,0 +1,129 @@
|
||||
import babelParser from "@babel/eslint-parser";
|
||||
import jsdoc from "eslint-plugin-jsdoc";
|
||||
import js from "@eslint/js";
|
||||
import globals from "globals";
|
||||
|
||||
export default [
|
||||
js.configs.recommended,
|
||||
{
|
||||
languageOptions: {
|
||||
ecmaVersion: 2022,
|
||||
parser: babelParser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 2022,
|
||||
ecmaFeatures: {
|
||||
impliedStrict: true
|
||||
},
|
||||
sourceType: "module",
|
||||
allowImportExportEverywhere: true
|
||||
},
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node,
|
||||
...globals.es6,
|
||||
"$": false,
|
||||
"jQuery": false,
|
||||
"log": false,
|
||||
"app": false,
|
||||
|
||||
"COMPILE_TIME": false,
|
||||
"COMPILE_MSG": false,
|
||||
"PKG_VERSION": false
|
||||
},
|
||||
},
|
||||
ignores: ["src/core/vendor/**"],
|
||||
plugins: {
|
||||
jsdoc
|
||||
},
|
||||
rules: {
|
||||
// enable additional rules
|
||||
"no-eval": "error",
|
||||
"no-implied-eval": "error",
|
||||
"dot-notation": "error",
|
||||
"eqeqeq": ["error", "smart"],
|
||||
"no-caller": "error",
|
||||
"no-extra-bind": "error",
|
||||
"no-unused-expressions": "error",
|
||||
"no-useless-call": "error",
|
||||
"no-useless-return": "error",
|
||||
"radix": "warn",
|
||||
|
||||
// modify rules from base configurations
|
||||
"no-unused-vars": ["error", {
|
||||
"args": "none",
|
||||
"vars": "all",
|
||||
"caughtErrors": "none"
|
||||
}],
|
||||
"no-empty": ["error", {
|
||||
"allowEmptyCatch": true
|
||||
}],
|
||||
|
||||
// disable rules from base configurations
|
||||
"no-control-regex": "off",
|
||||
"require-atomic-updates": "off",
|
||||
"no-async-promise-executor": "off",
|
||||
|
||||
// stylistic conventions
|
||||
"brace-style": ["error", "1tbs"],
|
||||
"space-before-blocks": ["error", "always"],
|
||||
"block-spacing": "error",
|
||||
"array-bracket-spacing": "error",
|
||||
"comma-spacing": "error",
|
||||
"spaced-comment": ["error", "always", { "exceptions": ["/"] }],
|
||||
"comma-style": "error",
|
||||
"computed-property-spacing": "error",
|
||||
"no-trailing-spaces": "warn",
|
||||
"eol-last": "error",
|
||||
"func-call-spacing": "error",
|
||||
"key-spacing": ["warn", {
|
||||
"mode": "minimum"
|
||||
}],
|
||||
"indent": ["error", 4, {
|
||||
"ignoreComments": true,
|
||||
"ArrayExpression": "first",
|
||||
"SwitchCase": 1
|
||||
}],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"quotes": ["error", "double", {
|
||||
"avoidEscape": true,
|
||||
"allowTemplateLiterals": true
|
||||
}],
|
||||
"camelcase": ["error", {
|
||||
"properties": "always"
|
||||
}],
|
||||
"semi": ["error", "always"],
|
||||
"unicode-bom": "error",
|
||||
"jsdoc/require-jsdoc": ["error", {
|
||||
"require": {
|
||||
"FunctionDeclaration": true,
|
||||
"MethodDefinition": true,
|
||||
"ClassDeclaration": true,
|
||||
"ArrowFunctionExpression": false
|
||||
}
|
||||
}],
|
||||
"keyword-spacing": ["error", {
|
||||
"before": true,
|
||||
"after": true
|
||||
}],
|
||||
"no-multiple-empty-lines": ["warn", {
|
||||
"max": 2,
|
||||
"maxEOF": 1,
|
||||
"maxBOF": 0
|
||||
}],
|
||||
"no-whitespace-before-property": "error",
|
||||
"operator-linebreak": ["error", "after"],
|
||||
"space-in-parens": "error",
|
||||
"no-var": "error",
|
||||
"prefer-const": "error",
|
||||
"no-console": "error"
|
||||
},
|
||||
},
|
||||
// File-pattern specific overrides
|
||||
{
|
||||
files: ["tests/**/*"],
|
||||
rules: {
|
||||
"no-unused-expressions": "off",
|
||||
"no-console": "off"
|
||||
}
|
||||
},
|
||||
];
|
||||
4568
package-lock.json
generated
4568
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
87
package.json
87
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cyberchef",
|
||||
"version": "10.8.0",
|
||||
"version": "10.19.4",
|
||||
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
|
||||
"author": "n1474335 <n1474335@gmail.com>",
|
||||
"homepage": "https://gchq.github.io/CyberChef",
|
||||
@@ -39,29 +39,32 @@
|
||||
"node >= 16"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.23.9",
|
||||
"@babel/eslint-parser": "^7.23.10",
|
||||
"@babel/plugin-syntax-import-assertions": "^7.23.3",
|
||||
"@babel/plugin-transform-runtime": "^7.23.9",
|
||||
"@babel/preset-env": "^7.23.9",
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@codemirror/commands": "^6.3.3",
|
||||
"@codemirror/language": "^6.10.1",
|
||||
"@codemirror/search": "^6.5.5",
|
||||
"@codemirror/state": "^6.4.0",
|
||||
"@codemirror/view": "^6.23.1",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"@babel/core": "^7.24.7",
|
||||
"@babel/eslint-parser": "^7.24.7",
|
||||
"@babel/plugin-syntax-import-assertions": "^7.24.7",
|
||||
"@babel/plugin-transform-runtime": "^7.24.7",
|
||||
"@babel/preset-env": "^7.24.7",
|
||||
"@babel/runtime": "^7.24.7",
|
||||
"@codemirror/commands": "^6.6.0",
|
||||
"@codemirror/language": "^6.10.2",
|
||||
"@codemirror/search": "^6.5.6",
|
||||
"@codemirror/state": "^6.4.1",
|
||||
"@codemirror/view": "^6.28.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"babel-loader": "^9.1.3",
|
||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||
"babel-plugin-transform-builtin-extend": "1.1.2",
|
||||
"base64-loader": "^1.0.0",
|
||||
"chromedriver": "^121.0.0",
|
||||
"chromedriver": "^130.0.0",
|
||||
"cli-progress": "^3.12.0",
|
||||
"colors": "^1.4.0",
|
||||
"compression-webpack-plugin": "^11.1.0",
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
"core-js": "^3.35.1",
|
||||
"css-loader": "6.10.0",
|
||||
"eslint": "^8.56.0",
|
||||
"core-js": "^3.37.1",
|
||||
"css-loader": "7.1.2",
|
||||
"eslint": "^9.4.0",
|
||||
"eslint-plugin-jsdoc": "^48.2.9",
|
||||
"globals": "^15.4.0",
|
||||
"grunt": "^1.6.1",
|
||||
"grunt-chmod": "~1.1.1",
|
||||
"grunt-concurrent": "^3.0.0",
|
||||
@@ -69,25 +72,25 @@
|
||||
"grunt-contrib-connect": "^4.0.0",
|
||||
"grunt-contrib-copy": "~1.0.0",
|
||||
"grunt-contrib-watch": "^1.1.0",
|
||||
"grunt-eslint": "^24.3.0",
|
||||
"grunt-eslint": "^25.0.0",
|
||||
"grunt-exec": "~3.0.0",
|
||||
"grunt-webpack": "^6.0.0",
|
||||
"grunt-zip": "^1.0.0",
|
||||
"html-webpack-plugin": "^5.6.0",
|
||||
"imports-loader": "^5.0.0",
|
||||
"mini-css-extract-plugin": "2.8.0",
|
||||
"modify-source-webpack-plugin": "^3.0.0",
|
||||
"nightwatch": "^3.4.0",
|
||||
"postcss": "^8.4.33",
|
||||
"mini-css-extract-plugin": "2.9.0",
|
||||
"modify-source-webpack-plugin": "^4.1.0",
|
||||
"nightwatch": "^3.6.3",
|
||||
"postcss": "^8.4.38",
|
||||
"postcss-css-variables": "^0.19.0",
|
||||
"postcss-import": "^16.0.0",
|
||||
"postcss-loader": "^8.1.0",
|
||||
"postcss-import": "^16.1.0",
|
||||
"postcss-loader": "^8.1.1",
|
||||
"prompt": "^1.3.0",
|
||||
"sitemap": "^7.1.1",
|
||||
"terser": "^5.27.0",
|
||||
"webpack": "^5.90.1",
|
||||
"webpack-bundle-analyzer": "^4.10.1",
|
||||
"webpack-dev-server": "4.15.1",
|
||||
"sitemap": "^8.0.0",
|
||||
"terser": "^5.31.1",
|
||||
"webpack": "^5.91.0",
|
||||
"webpack-bundle-analyzer": "^4.10.2",
|
||||
"webpack-dev-server": "5.0.4",
|
||||
"webpack-node-externals": "^3.0.0",
|
||||
"worker-loader": "^3.0.8"
|
||||
},
|
||||
@@ -96,6 +99,7 @@
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@blu3r4y/lzma": "^2.3.3",
|
||||
"@wavesenterprise/crypto-gost-js": "^2.1.0-RC1",
|
||||
"@xmldom/xmldom": "^0.8.10",
|
||||
"argon2-browser": "^1.18.0",
|
||||
"arrive": "^2.4.1",
|
||||
"avsc": "^5.7.7",
|
||||
@@ -115,24 +119,26 @@
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"crypto-js": "^4.2.0",
|
||||
"ctph.js": "0.0.5",
|
||||
"d3": "7.8.5",
|
||||
"d3": "7.9.0",
|
||||
"d3-hexbin": "^0.2.2",
|
||||
"diff": "^5.1.0",
|
||||
"diff": "^5.2.0",
|
||||
"es6-promisify": "^7.0.0",
|
||||
"escodegen": "^2.1.0",
|
||||
"esprima": "^4.0.1",
|
||||
"exif-parser": "^0.1.12",
|
||||
"fernet": "^0.4.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"flat": "^6.0.1",
|
||||
"geodesy": "1.1.3",
|
||||
"highlight.js": "^11.9.0",
|
||||
"jimp": "^0.16.13",
|
||||
"ieee754": "^1.2.1",
|
||||
"jimp": "^0.22.12",
|
||||
"jquery": "3.7.1",
|
||||
"js-crc": "^0.2.0",
|
||||
"js-sha3": "^0.9.3",
|
||||
"jsesc": "^3.0.2",
|
||||
"json5": "^2.2.3",
|
||||
"jsonpath-plus": "^8.0.0",
|
||||
"jsonpath-plus": "^9.0.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"jsqr": "^1.4.0",
|
||||
"jsrsasign": "^11.1.0",
|
||||
@@ -144,9 +150,9 @@
|
||||
"loglevel-message-prefix": "^3.0.0",
|
||||
"lz-string": "^1.5.0",
|
||||
"lz4js": "^0.2.0",
|
||||
"markdown-it": "^14.0.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.44",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"ngeohash": "^0.6.3",
|
||||
"node-forge": "^1.3.1",
|
||||
"node-md6": "^0.1.0",
|
||||
@@ -158,9 +164,9 @@
|
||||
"path": "^0.12.7",
|
||||
"popper.js": "^1.16.1",
|
||||
"process": "^0.11.10",
|
||||
"protobufjs": "^7.2.6",
|
||||
"protobufjs": "^7.3.1",
|
||||
"qr-image": "^3.2.0",
|
||||
"reflect-metadata": "^0.2.1",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rison": "^0.1.1",
|
||||
"scryptsy": "^2.1.0",
|
||||
"snackbarjs": "^1.1.0",
|
||||
@@ -168,12 +174,11 @@
|
||||
"split.js": "^1.6.5",
|
||||
"ssdeep.js": "0.0.3",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"tesseract.js": "5.0.4",
|
||||
"ua-parser-js": "^1.0.37",
|
||||
"tesseract.js": "5.1.0",
|
||||
"ua-parser-js": "^1.0.38",
|
||||
"unorm": "^1.6.0",
|
||||
"utf8": "^3.0.0",
|
||||
"vkbeautify": "^0.99.3",
|
||||
"xmldom": "^0.6.0",
|
||||
"xpath": "0.0.34",
|
||||
"xregexp": "^5.1.1",
|
||||
"zlibjs": "^0.3.1"
|
||||
@@ -188,7 +193,7 @@
|
||||
"testui": "npx grunt testui",
|
||||
"testuidev": "npx nightwatch --env=dev",
|
||||
"lint": "npx grunt lint",
|
||||
"postinstall": "npx grunt exec:fixCryptoApiImports && npx grunt exec:fixSnackbarMarkup",
|
||||
"postinstall": "npx grunt exec:fixCryptoApiImports && npx grunt exec:fixSnackbarMarkup && npx grunt exec:fixJimpModule",
|
||||
"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`)'",
|
||||
|
||||
@@ -893,7 +893,7 @@ class Utils {
|
||||
|
||||
|
||||
/**
|
||||
* Converts a string to it's title case equivalent.
|
||||
* Converts a string to its title case equivalent.
|
||||
*
|
||||
* @param {string} str
|
||||
* @returns string
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
"From Charcode",
|
||||
"To Decimal",
|
||||
"From Decimal",
|
||||
"To Float",
|
||||
"From Float",
|
||||
"To Binary",
|
||||
"From Binary",
|
||||
"To Octal",
|
||||
@@ -70,6 +72,7 @@
|
||||
"Avro to JSON",
|
||||
"CBOR Encode",
|
||||
"CBOR Decode",
|
||||
"Caret/M-decode",
|
||||
"Rison Encode",
|
||||
"Rison Decode"
|
||||
]
|
||||
@@ -85,6 +88,8 @@
|
||||
"DES Decrypt",
|
||||
"Triple DES Encrypt",
|
||||
"Triple DES Decrypt",
|
||||
"Fernet Encrypt",
|
||||
"Fernet Decrypt",
|
||||
"LS47 Encrypt",
|
||||
"LS47 Decrypt",
|
||||
"RC2 Encrypt",
|
||||
@@ -92,6 +97,8 @@
|
||||
"RC4",
|
||||
"RC4 Drop",
|
||||
"ChaCha",
|
||||
"Salsa20",
|
||||
"XSalsa20",
|
||||
"Rabbit",
|
||||
"SM4 Encrypt",
|
||||
"SM4 Decrypt",
|
||||
@@ -110,6 +117,8 @@
|
||||
"XOR Brute Force",
|
||||
"Vigenère Encode",
|
||||
"Vigenère Decode",
|
||||
"XXTEA Encrypt",
|
||||
"XXTEA Decrypt",
|
||||
"To Morse Code",
|
||||
"From Morse Code",
|
||||
"Bacon Cipher Encode",
|
||||
@@ -155,11 +164,14 @@
|
||||
"name": "Public Key",
|
||||
"ops": [
|
||||
"Parse X.509 certificate",
|
||||
"Parse X.509 CRL",
|
||||
"Parse ASN.1 hex string",
|
||||
"PEM to Hex",
|
||||
"Hex to PEM",
|
||||
"Hex to Object Identifier",
|
||||
"Object Identifier to Hex",
|
||||
"PEM to JWK",
|
||||
"JWK to PEM",
|
||||
"Generate PGP Key Pair",
|
||||
"PGP Encrypt",
|
||||
"PGP Decrypt",
|
||||
@@ -171,7 +183,14 @@
|
||||
"RSA Verify",
|
||||
"RSA Encrypt",
|
||||
"RSA Decrypt",
|
||||
"Parse SSH Host Key"
|
||||
"Generate ECDSA Key Pair",
|
||||
"ECDSA Signature Conversion",
|
||||
"ECDSA Sign",
|
||||
"ECDSA Verify",
|
||||
"Parse SSH Host Key",
|
||||
"Parse CSR",
|
||||
"Public Key from Certificate",
|
||||
"Public Key from Private Key"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -217,7 +236,10 @@
|
||||
"Parse IPv6 address",
|
||||
"Parse IPv4 header",
|
||||
"Parse TCP",
|
||||
"Strip TCP header",
|
||||
"Parse TLS record",
|
||||
"Parse UDP",
|
||||
"Strip UDP header",
|
||||
"Parse SSH Host Key",
|
||||
"Parse URI",
|
||||
"URL Encode",
|
||||
@@ -228,6 +250,8 @@
|
||||
"VarInt Decode",
|
||||
"JA3 Fingerprint",
|
||||
"JA3S Fingerprint",
|
||||
"JA4 Fingerprint",
|
||||
"JA4Server Fingerprint",
|
||||
"HASSH Client Fingerprint",
|
||||
"HASSH Server Fingerprint",
|
||||
"Format MAC addresses",
|
||||
@@ -236,6 +260,7 @@
|
||||
"Encode NetBIOS Name",
|
||||
"Decode NetBIOS Name",
|
||||
"Defang URL",
|
||||
"Fang URL",
|
||||
"Defang IP Addresses"
|
||||
]
|
||||
},
|
||||
@@ -311,6 +336,7 @@
|
||||
"To UNIX Timestamp",
|
||||
"Windows Filetime to UNIX Timestamp",
|
||||
"UNIX Timestamp to Windows Filetime",
|
||||
"DateTime Delta",
|
||||
"Extract dates",
|
||||
"Get Time",
|
||||
"Sleep"
|
||||
@@ -327,13 +353,15 @@
|
||||
"Extract domains",
|
||||
"Extract file paths",
|
||||
"Extract dates",
|
||||
"Extract hashes",
|
||||
"Regular expression",
|
||||
"XPath expression",
|
||||
"JPath expression",
|
||||
"CSS selector",
|
||||
"Extract EXIF",
|
||||
"Extract ID3",
|
||||
"Extract Files"
|
||||
"Extract Files",
|
||||
"RAKE"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -374,7 +402,6 @@
|
||||
"SHA2",
|
||||
"SHA3",
|
||||
"SM3",
|
||||
"MurmurHash3",
|
||||
"Keccak",
|
||||
"Shake",
|
||||
"RIPEMD",
|
||||
@@ -399,6 +426,7 @@
|
||||
"Scrypt",
|
||||
"NT Hash",
|
||||
"LM Hash",
|
||||
"MurmurHash3",
|
||||
"Fletcher-8 Checksum",
|
||||
"Fletcher-16 Checksum",
|
||||
"Fletcher-32 Checksum",
|
||||
|
||||
@@ -30,12 +30,12 @@ fs.readdirSync(path.join(dir, "../operations")).forEach(file => {
|
||||
|
||||
// Construct index file
|
||||
let code = `/**
|
||||
* THIS FILE IS AUTOMATICALLY GENERATED BY src/core/config/scripts/generateOpsIndex.mjs
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright ${new Date().getUTCFullYear()}
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
* THIS FILE IS AUTOMATICALLY GENERATED BY src/core/config/scripts/generateOpsIndex.mjs
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright ${new Date().getUTCFullYear()}
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
`;
|
||||
|
||||
opObjs.forEach(obj => {
|
||||
|
||||
@@ -147,7 +147,7 @@ class ${moduleName} extends Operation {
|
||||
this.name = "${result.opName}";
|
||||
this.module = "${result.module}";
|
||||
this.description = "${(new EscapeString).run(result.description, ["Special chars", "Double"])}";
|
||||
this.infoURL = "${result.infoURL}";
|
||||
this.infoURL = "${result.infoURL}"; // Usually a Wikipedia link. Remember to remove localisation (i.e. https://wikipedia.org/etc rather than https://en.wikipedia.org/etc)
|
||||
this.inputType = "${result.inputType}";
|
||||
this.outputType = "${result.outputType}";
|
||||
this.args = [
|
||||
|
||||
@@ -224,8 +224,85 @@ export function chrEncWidth(page) {
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
export const UNICODE_NORMALISATION_FORMS = ["NFD", "NFC", "NFKD", "NFKC"];
|
||||
|
||||
|
||||
/**
|
||||
* Character encoding format mappings.
|
||||
* Detects whether the input buffer is valid UTF8.
|
||||
*
|
||||
* @param {ArrayBuffer} data
|
||||
* @returns {number} - 0 = not UTF8, 1 = ASCII, 2 = UTF8
|
||||
*/
|
||||
export const UNICODE_NORMALISATION_FORMS = ["NFD", "NFC", "NFKD", "NFKC"];
|
||||
export function isUTF8(data) {
|
||||
const bytes = new Uint8Array(data);
|
||||
let i = 0;
|
||||
let onlyASCII = true;
|
||||
while (i < bytes.length) {
|
||||
if (( // ASCII
|
||||
bytes[i] === 0x09 ||
|
||||
bytes[i] === 0x0A ||
|
||||
bytes[i] === 0x0D ||
|
||||
(0x20 <= bytes[i] && bytes[i] <= 0x7E)
|
||||
)) {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
onlyASCII = false;
|
||||
|
||||
if (( // non-overlong 2-byte
|
||||
(0xC2 <= bytes[i] && bytes[i] <= 0xDF) &&
|
||||
(0x80 <= bytes[i+1] && bytes[i+1] <= 0xBF)
|
||||
)) {
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (( // excluding overlongs
|
||||
bytes[i] === 0xE0 &&
|
||||
(0xA0 <= bytes[i + 1] && bytes[i + 1] <= 0xBF) &&
|
||||
(0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF)
|
||||
) ||
|
||||
( // straight 3-byte
|
||||
((0xE1 <= bytes[i] && bytes[i] <= 0xEC) ||
|
||||
bytes[i] === 0xEE ||
|
||||
bytes[i] === 0xEF) &&
|
||||
(0x80 <= bytes[i + 1] && bytes[i+1] <= 0xBF) &&
|
||||
(0x80 <= bytes[i+2] && bytes[i+2] <= 0xBF)
|
||||
) ||
|
||||
( // excluding surrogates
|
||||
bytes[i] === 0xED &&
|
||||
(0x80 <= bytes[i+1] && bytes[i+1] <= 0x9F) &&
|
||||
(0x80 <= bytes[i+2] && bytes[i+2] <= 0xBF)
|
||||
)) {
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (( // planes 1-3
|
||||
bytes[i] === 0xF0 &&
|
||||
(0x90 <= bytes[i + 1] && bytes[i + 1] <= 0xBF) &&
|
||||
(0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF) &&
|
||||
(0x80 <= bytes[i + 3] && bytes[i + 3] <= 0xBF)
|
||||
) ||
|
||||
( // planes 4-15
|
||||
(0xF1 <= bytes[i] && bytes[i] <= 0xF3) &&
|
||||
(0x80 <= bytes[i + 1] && bytes[i + 1] <= 0xBF) &&
|
||||
(0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF) &&
|
||||
(0x80 <= bytes[i + 3] && bytes[i + 3] <= 0xBF)
|
||||
) ||
|
||||
( // plane 16
|
||||
bytes[i] === 0xF4 &&
|
||||
(0x80 <= bytes[i + 1] && bytes[i + 1] <= 0x8F) &&
|
||||
(0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF) &&
|
||||
(0x80 <= bytes[i + 3] && bytes[i + 3] <= 0xBF)
|
||||
)) {
|
||||
i += 4;
|
||||
continue;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return onlyASCII ? 1 : 2;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
export function encode(tempIVP, key, rounds, input) {
|
||||
const ivp = new Uint8Array(key.concat(tempIVP));
|
||||
const ivp = new Uint8Array([...key, ...tempIVP]);
|
||||
const state = new Array(256).fill(0);
|
||||
let j = 0, i = 0;
|
||||
const result = [];
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*
|
||||
* @author Matt C [matt@artemisbot.uk]
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @author Evie H [evie@evie.sh]
|
||||
*
|
||||
* @copyright Crown Copyright 2018
|
||||
* @license Apache-2.0
|
||||
@@ -10,6 +11,7 @@
|
||||
*/
|
||||
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import CryptoJS from "crypto-js";
|
||||
|
||||
/**
|
||||
@@ -30,6 +32,10 @@ export function affineEncode(input, args) {
|
||||
throw new OperationError("The values of a and b can only be integers.");
|
||||
}
|
||||
|
||||
if (Utils.gcd(a, 26) !== 1) {
|
||||
throw new OperationError("The value of `a` must be coprime to 26.");
|
||||
}
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
if (alphabet.indexOf(input[i]) >= 0) {
|
||||
// Uses the affine function ax+b % m = y (where m is length of the alphabet)
|
||||
|
||||
@@ -72,6 +72,27 @@ export const FILE_SIGNATURES = {
|
||||
},
|
||||
extractor: extractWEBP
|
||||
},
|
||||
{
|
||||
name: "High Efficiency Image File Format",
|
||||
extension: "heic,heif",
|
||||
mime: "image/heif",
|
||||
description: "",
|
||||
signature: {
|
||||
0: 0x00,
|
||||
1: 0x00,
|
||||
2: 0x00,
|
||||
3: [0x24, 0x18],
|
||||
4: 0x66, // ftypheic
|
||||
5: 0x74,
|
||||
6: 0x79,
|
||||
7: 0x70,
|
||||
8: 0x68,
|
||||
9: 0x65,
|
||||
10: 0x69,
|
||||
11: 0x63
|
||||
},
|
||||
extractor: null
|
||||
},
|
||||
{
|
||||
name: "Camera Image File Format",
|
||||
extension: "crw",
|
||||
@@ -2727,7 +2748,7 @@ export function extractGIF(bytes, offset) {
|
||||
stream.moveForwardsBy(11);
|
||||
|
||||
// Loop until next Graphic Control Extension.
|
||||
while (stream.getBytes(2) !== [0x21, 0xf9]) {
|
||||
while (!Array.from(stream.getBytes(2)).equals([0x21, 0xf9])) {
|
||||
stream.moveBackwardsBy(2);
|
||||
stream.moveForwardsBy(stream.readInt(1));
|
||||
if (!stream.readInt(1))
|
||||
|
||||
264
src/core/lib/JA4.mjs
Normal file
264
src/core/lib/JA4.mjs
Normal file
@@ -0,0 +1,264 @@
|
||||
/**
|
||||
* JA4 resources.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* JA4 Copyright 2023 FoxIO, LLC.
|
||||
* @license BSD-3-Clause
|
||||
*/
|
||||
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import { parseTLSRecord, parseHighestSupportedVersion, parseFirstALPNValue } from "./TLS.mjs";
|
||||
import { toHexFast } from "./Hex.mjs";
|
||||
import { runHash } from "./Hash.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the JA4 from a given TLS Client Hello Stream
|
||||
* @param {Uint8Array} bytes
|
||||
* @returns {string}
|
||||
*/
|
||||
export function toJA4(bytes) {
|
||||
let tlsr = {};
|
||||
try {
|
||||
tlsr = parseTLSRecord(bytes);
|
||||
if (tlsr.handshake.value.handshakeType.value !== 0x01) {
|
||||
throw new Error();
|
||||
}
|
||||
} catch (err) {
|
||||
throw new OperationError("Data is not a valid TLS Client Hello. QUIC is not yet supported.\n" + err);
|
||||
}
|
||||
|
||||
/* QUIC
|
||||
“q” or “t”, which denotes whether the hello packet is for QUIC or TCP.
|
||||
TODO: Implement QUIC
|
||||
*/
|
||||
const ptype = "t";
|
||||
|
||||
/* TLS Version
|
||||
TLS version is shown in 3 different places. If extension 0x002b exists (supported_versions), then the version
|
||||
is the highest value in the extension. Remember to ignore GREASE values. If the extension doesn’t exist, then
|
||||
the TLS version is the value of the Protocol Version. Handshake version (located at the top of the packet)
|
||||
should be ignored.
|
||||
*/
|
||||
let version = tlsr.handshake.value.helloVersion.value;
|
||||
for (const ext of tlsr.handshake.value.extensions.value) {
|
||||
if (ext.type.value === "supported_versions") {
|
||||
version = parseHighestSupportedVersion(ext.value.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
version = tlsVersionMapper(version);
|
||||
|
||||
/* SNI
|
||||
If the SNI extension (0x0000) exists, then the destination of the connection is a domain, or “d” in the fingerprint.
|
||||
If the SNI does not exist, then the destination is an IP address, or “i”.
|
||||
*/
|
||||
let sni = "i";
|
||||
for (const ext of tlsr.handshake.value.extensions.value) {
|
||||
if (ext.type.value === "server_name") {
|
||||
sni = "d";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Number of Ciphers
|
||||
2 character number of cipher suites, so if there’s 6 cipher suites in the hello packet, then the value should be “06”.
|
||||
If there’s > 99, which there should never be, then output “99”. Remember, ignore GREASE values. They don’t count.
|
||||
*/
|
||||
let cipherLen = 0;
|
||||
for (const cs of tlsr.handshake.value.cipherSuites.value) {
|
||||
if (cs.value !== "GREASE") cipherLen++;
|
||||
}
|
||||
cipherLen = cipherLen > 99 ? "99" : cipherLen.toString().padStart(2, "0");
|
||||
|
||||
/* Number of Extensions
|
||||
Same as counting ciphers. Ignore GREASE. Include SNI and ALPN.
|
||||
*/
|
||||
let extLen = 0;
|
||||
for (const ext of tlsr.handshake.value.extensions.value) {
|
||||
if (ext.type.value !== "GREASE") extLen++;
|
||||
}
|
||||
extLen = extLen > 99 ? "99" : extLen.toString().padStart(2, "0");
|
||||
|
||||
/* ALPN Extension Value
|
||||
The first and last characters of the ALPN (Application-Layer Protocol Negotiation) first value.
|
||||
If there are no ALPN values or no ALPN extension then we print “00” as the value in the fingerprint.
|
||||
*/
|
||||
let alpn = "00";
|
||||
for (const ext of tlsr.handshake.value.extensions.value) {
|
||||
if (ext.type.value === "application_layer_protocol_negotiation") {
|
||||
alpn = parseFirstALPNValue(ext.value.data);
|
||||
alpn = alpn.charAt(0) + alpn.charAt(alpn.length - 1);
|
||||
if (alpn.charCodeAt(0) > 127) alpn = "99";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Cipher hash
|
||||
A 12 character truncated sha256 hash of the list of ciphers sorted in hex order, first 12 characters.
|
||||
The list is created using the 4 character hex values of the ciphers, lower case, comma delimited, ignoring GREASE.
|
||||
*/
|
||||
const originalCiphersList = [];
|
||||
for (const cs of tlsr.handshake.value.cipherSuites.value) {
|
||||
if (cs.value !== "GREASE") {
|
||||
originalCiphersList.push(toHexFast(cs.data));
|
||||
}
|
||||
}
|
||||
const sortedCiphersList = [...originalCiphersList].sort();
|
||||
const sortedCiphersRaw = sortedCiphersList.join(",");
|
||||
const originalCiphersRaw = originalCiphersList.join(",");
|
||||
const sortedCiphers = runHash(
|
||||
"sha256",
|
||||
Utils.strToArrayBuffer(sortedCiphersRaw)
|
||||
).substring(0, 12);
|
||||
const originalCiphers = runHash(
|
||||
"sha256",
|
||||
Utils.strToArrayBuffer(originalCiphersRaw)
|
||||
).substring(0, 12);
|
||||
|
||||
/* Extension hash
|
||||
A 12 character truncated sha256 hash of the list of extensions, sorted by hex value, followed by the list of signature
|
||||
algorithms, in the order that they appear (not sorted).
|
||||
The extension list is created using the 4 character hex values of the extensions, lower case, comma delimited, sorted
|
||||
(not in the order they appear). Ignore the SNI extension (0000) and the ALPN extension (0010) as we’ve already captured
|
||||
them in the a section of the fingerprint. These values are omitted so that the same application would have the same b
|
||||
section of the fingerprint regardless of if it were going to a domain, IP, or changing ALPNs.
|
||||
*/
|
||||
const originalExtensionsList = [];
|
||||
let signatureAlgorithms = "";
|
||||
for (const ext of tlsr.handshake.value.extensions.value) {
|
||||
if (ext.type.value !== "GREASE") {
|
||||
originalExtensionsList.push(toHexFast(ext.type.data));
|
||||
}
|
||||
if (ext.type.value === "signature_algorithms") {
|
||||
signatureAlgorithms = toHexFast(ext.value.data.slice(2));
|
||||
signatureAlgorithms = signatureAlgorithms.replace(/(.{4})/g, "$1,");
|
||||
signatureAlgorithms = signatureAlgorithms.substring(0, signatureAlgorithms.length - 1);
|
||||
}
|
||||
}
|
||||
const sortedExtensionsList = [...originalExtensionsList].filter(e => e !== "0000" && e !== "0010").sort();
|
||||
const sortedExtensionsRaw = sortedExtensionsList.join(",") + "_" + signatureAlgorithms;
|
||||
const originalExtensionsRaw = originalExtensionsList.join(",") + "_" + signatureAlgorithms;
|
||||
const sortedExtensions = runHash(
|
||||
"sha256",
|
||||
Utils.strToArrayBuffer(sortedExtensionsRaw)
|
||||
).substring(0, 12);
|
||||
const originalExtensions = runHash(
|
||||
"sha256",
|
||||
Utils.strToArrayBuffer(originalExtensionsRaw)
|
||||
).substring(0, 12);
|
||||
|
||||
return {
|
||||
"JA4": `${ptype}${version}${sni}${cipherLen}${extLen}${alpn}_${sortedCiphers}_${sortedExtensions}`,
|
||||
"JA4_o": `${ptype}${version}${sni}${cipherLen}${extLen}${alpn}_${originalCiphers}_${originalExtensions}`,
|
||||
"JA4_r": `${ptype}${version}${sni}${cipherLen}${extLen}${alpn}_${sortedCiphersRaw}_${sortedExtensionsRaw}`,
|
||||
"JA4_ro": `${ptype}${version}${sni}${cipherLen}${extLen}${alpn}_${originalCiphersRaw}_${originalExtensionsRaw}`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the JA4Server from a given TLS Server Hello Stream
|
||||
* @param {Uint8Array} bytes
|
||||
* @returns {string}
|
||||
*/
|
||||
export function toJA4S(bytes) {
|
||||
let tlsr = {};
|
||||
try {
|
||||
tlsr = parseTLSRecord(bytes);
|
||||
if (tlsr.handshake.value.handshakeType.value !== 0x02) {
|
||||
throw new Error();
|
||||
}
|
||||
} catch (err) {
|
||||
throw new OperationError("Data is not a valid TLS Server Hello. QUIC is not yet supported.\n" + err);
|
||||
}
|
||||
|
||||
/* QUIC
|
||||
“q” or “t”, which denotes whether the hello packet is for QUIC or TCP.
|
||||
TODO: Implement QUIC
|
||||
*/
|
||||
const ptype = "t";
|
||||
|
||||
/* TLS Version
|
||||
TLS version is shown in 3 different places. If extension 0x002b exists (supported_versions), then the version
|
||||
is the highest value in the extension. Remember to ignore GREASE values. If the extension doesn’t exist, then
|
||||
the TLS version is the value of the Protocol Version. Handshake version (located at the top of the packet)
|
||||
should be ignored.
|
||||
*/
|
||||
let version = tlsr.handshake.value.helloVersion.value;
|
||||
for (const ext of tlsr.handshake.value.extensions.value) {
|
||||
if (ext.type.value === "supported_versions") {
|
||||
version = parseHighestSupportedVersion(ext.value.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
version = tlsVersionMapper(version);
|
||||
|
||||
/* Number of Extensions
|
||||
2 character number of cipher suites, so if there’s 6 cipher suites in the hello packet, then the value should be “06”.
|
||||
If there’s > 99, which there should never be, then output “99”.
|
||||
*/
|
||||
let extLen = tlsr.handshake.value.extensions.value.length;
|
||||
extLen = extLen > 99 ? "99" : extLen.toString().padStart(2, "0");
|
||||
|
||||
/* ALPN Extension Chosen Value
|
||||
The first and last characters of the ALPN (Application-Layer Protocol Negotiation) first value.
|
||||
If there are no ALPN values or no ALPN extension then we print “00” as the value in the fingerprint.
|
||||
*/
|
||||
let alpn = "00";
|
||||
for (const ext of tlsr.handshake.value.extensions.value) {
|
||||
if (ext.type.value === "application_layer_protocol_negotiation") {
|
||||
alpn = parseFirstALPNValue(ext.value.data);
|
||||
alpn = alpn.charAt(0) + alpn.charAt(alpn.length - 1);
|
||||
if (alpn.charCodeAt(0) > 127) alpn = "99";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Chosen Cipher
|
||||
The hex value of the chosen cipher suite
|
||||
*/
|
||||
const cipher = toHexFast(tlsr.handshake.value.cipherSuite.data);
|
||||
|
||||
/* Extension hash
|
||||
A 12 character truncated sha256 hash of the list of extensions.
|
||||
The extension list is created using the 4 character hex values of the extensions, lower case, comma delimited.
|
||||
*/
|
||||
const extensionsList = [];
|
||||
for (const ext of tlsr.handshake.value.extensions.value) {
|
||||
extensionsList.push(toHexFast(ext.type.data));
|
||||
}
|
||||
const extensionsRaw = extensionsList.join(",");
|
||||
const extensionsHash = runHash(
|
||||
"sha256",
|
||||
Utils.strToArrayBuffer(extensionsRaw)
|
||||
).substring(0, 12);
|
||||
|
||||
return {
|
||||
"JA4S": `${ptype}${version}${extLen}${alpn}_${cipher}_${extensionsHash}`,
|
||||
"JA4S_r": `${ptype}${version}${extLen}${alpn}_${cipher}_${extensionsRaw}`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Takes a TLS version value and returns a JA4 TLS version string
|
||||
* @param {Uint8Array} version - Two byte array of version number
|
||||
* @returns {string}
|
||||
*/
|
||||
function tlsVersionMapper(version) {
|
||||
switch (version) {
|
||||
case 0x0304: return "13"; // TLS 1.3
|
||||
case 0x0303: return "12"; // TLS 1.2
|
||||
case 0x0302: return "11"; // TLS 1.1
|
||||
case 0x0301: return "10"; // TLS 1.0
|
||||
case 0x0300: return "s3"; // SSL 3.0
|
||||
case 0x0200: return "s2"; // SSL 2.0
|
||||
case 0x0100: return "s1"; // SSL 1.0
|
||||
default: return "00"; // Unknown
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import Utils, { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import Recipe from "../Recipe.mjs";
|
||||
import Dish from "../Dish.mjs";
|
||||
import {detectFileType, isType} from "./FileType.mjs";
|
||||
import {isUTF8} from "./ChrEnc.mjs";
|
||||
import chiSquared from "chi-squared";
|
||||
|
||||
/**
|
||||
@@ -111,82 +112,6 @@ class Magic {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects whether the input buffer is valid UTF8.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isUTF8() {
|
||||
const bytes = new Uint8Array(this.inputBuffer);
|
||||
let i = 0;
|
||||
while (i < bytes.length) {
|
||||
if (( // ASCII
|
||||
bytes[i] === 0x09 ||
|
||||
bytes[i] === 0x0A ||
|
||||
bytes[i] === 0x0D ||
|
||||
(0x20 <= bytes[i] && bytes[i] <= 0x7E)
|
||||
)) {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (( // non-overlong 2-byte
|
||||
(0xC2 <= bytes[i] && bytes[i] <= 0xDF) &&
|
||||
(0x80 <= bytes[i+1] && bytes[i+1] <= 0xBF)
|
||||
)) {
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (( // excluding overlongs
|
||||
bytes[i] === 0xE0 &&
|
||||
(0xA0 <= bytes[i + 1] && bytes[i + 1] <= 0xBF) &&
|
||||
(0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF)
|
||||
) ||
|
||||
( // straight 3-byte
|
||||
((0xE1 <= bytes[i] && bytes[i] <= 0xEC) ||
|
||||
bytes[i] === 0xEE ||
|
||||
bytes[i] === 0xEF) &&
|
||||
(0x80 <= bytes[i + 1] && bytes[i+1] <= 0xBF) &&
|
||||
(0x80 <= bytes[i+2] && bytes[i+2] <= 0xBF)
|
||||
) ||
|
||||
( // excluding surrogates
|
||||
bytes[i] === 0xED &&
|
||||
(0x80 <= bytes[i+1] && bytes[i+1] <= 0x9F) &&
|
||||
(0x80 <= bytes[i+2] && bytes[i+2] <= 0xBF)
|
||||
)) {
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (( // planes 1-3
|
||||
bytes[i] === 0xF0 &&
|
||||
(0x90 <= bytes[i + 1] && bytes[i + 1] <= 0xBF) &&
|
||||
(0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF) &&
|
||||
(0x80 <= bytes[i + 3] && bytes[i + 3] <= 0xBF)
|
||||
) ||
|
||||
( // planes 4-15
|
||||
(0xF1 <= bytes[i] && bytes[i] <= 0xF3) &&
|
||||
(0x80 <= bytes[i + 1] && bytes[i + 1] <= 0xBF) &&
|
||||
(0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF) &&
|
||||
(0x80 <= bytes[i + 3] && bytes[i + 3] <= 0xBF)
|
||||
) ||
|
||||
( // plane 16
|
||||
bytes[i] === 0xF4 &&
|
||||
(0x80 <= bytes[i + 1] && bytes[i + 1] <= 0x8F) &&
|
||||
(0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF) &&
|
||||
(0x80 <= bytes[i + 3] && bytes[i + 3] <= 0xBF)
|
||||
)) {
|
||||
i += 4;
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the Shannon entropy of the input data.
|
||||
*
|
||||
@@ -336,7 +261,7 @@ class Magic {
|
||||
data: this.inputStr.slice(0, 100),
|
||||
languageScores: this.detectLanguage(extLang),
|
||||
fileType: this.detectFileType(),
|
||||
isUTF8: this.isUTF8(),
|
||||
isUTF8: !!isUTF8(this.inputBuffer),
|
||||
entropy: this.calcEntropy(),
|
||||
matchingOps: matchingOps,
|
||||
useful: useful,
|
||||
|
||||
@@ -26,6 +26,9 @@ export function objToTable(obj, nested=false) {
|
||||
</tr>`;
|
||||
|
||||
for (const key in obj) {
|
||||
if (typeof obj[key] === "function")
|
||||
continue;
|
||||
|
||||
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>`;
|
||||
|
||||
@@ -10,7 +10,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||
import jsQR from "jsqr";
|
||||
import qr from "qr-image";
|
||||
import Utils from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Parses a QR code image from an image
|
||||
@@ -22,7 +22,7 @@ import jimp from "jimp";
|
||||
export async function parseQrCode(input, normalise) {
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error opening image. (${err})`);
|
||||
}
|
||||
@@ -33,8 +33,8 @@ export async function parseQrCode(input, normalise) {
|
||||
image.background(0xFFFFFFFF);
|
||||
image.normalize();
|
||||
image.greyscale();
|
||||
image = await image.getBufferAsync(jimp.MIME_JPEG);
|
||||
image = await jimp.read(image);
|
||||
image = await image.getBufferAsync(Jimp.MIME_JPEG);
|
||||
image = await Jimp.read(image);
|
||||
}
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error normalising image. (${err})`);
|
||||
|
||||
144
src/core/lib/Salsa20.mjs
Normal file
144
src/core/lib/Salsa20.mjs
Normal file
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* @author joostrijneveld [joost@joostrijneveld.nl]
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* Computes the Salsa20 permute function
|
||||
*
|
||||
* @param {byteArray} x
|
||||
* @param {integer} rounds
|
||||
*/
|
||||
function salsa20Permute(x, rounds) {
|
||||
/**
|
||||
* Macro to compute a 32-bit rotate-left operation
|
||||
*
|
||||
* @param {integer} x
|
||||
* @param {integer} n
|
||||
* @returns {integer}
|
||||
*/
|
||||
function ROL32(x, n) {
|
||||
return ((x << n) & 0xFFFFFFFF) | (x >>> (32 - n));
|
||||
}
|
||||
|
||||
/**
|
||||
* Macro to compute a single Salsa20 quarterround operation
|
||||
*
|
||||
* @param {integer} x
|
||||
* @param {integer} a
|
||||
* @param {integer} b
|
||||
* @param {integer} c
|
||||
* @param {integer} d
|
||||
* @returns {integer}
|
||||
*/
|
||||
function quarterround(x, a, b, c, d) {
|
||||
x[b] ^= ROL32((x[a] + x[d]) & 0xFFFFFFFF, 7);
|
||||
x[c] ^= ROL32((x[b] + x[a]) & 0xFFFFFFFF, 9);
|
||||
x[d] ^= ROL32((x[c] + x[b]) & 0xFFFFFFFF, 13);
|
||||
x[a] ^= ROL32((x[d] + x[c]) & 0xFFFFFFFF, 18);
|
||||
}
|
||||
|
||||
for (let i = 0; i < rounds / 2; i++) {
|
||||
quarterround(x, 0, 4, 8, 12);
|
||||
quarterround(x, 5, 9, 13, 1);
|
||||
quarterround(x, 10, 14, 2, 6);
|
||||
quarterround(x, 15, 3, 7, 11);
|
||||
quarterround(x, 0, 1, 2, 3);
|
||||
quarterround(x, 5, 6, 7, 4);
|
||||
quarterround(x, 10, 11, 8, 9);
|
||||
quarterround(x, 15, 12, 13, 14);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the Salsa20 block function
|
||||
*
|
||||
* @param {byteArray} key
|
||||
* @param {byteArray} nonce
|
||||
* @param {byteArray} counter
|
||||
* @param {integer} rounds
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
export function salsa20Block(key, nonce, counter, rounds) {
|
||||
const tau = "expand 16-byte k";
|
||||
const sigma = "expand 32-byte k";
|
||||
let state, c;
|
||||
if (key.length === 16) {
|
||||
c = Utils.strToByteArray(tau);
|
||||
key = key.concat(key);
|
||||
} else {
|
||||
c = Utils.strToByteArray(sigma);
|
||||
}
|
||||
|
||||
state = c.slice(0, 4);
|
||||
state = state.concat(key.slice(0, 16));
|
||||
state = state.concat(c.slice(4, 8));
|
||||
state = state.concat(nonce);
|
||||
state = state.concat(counter);
|
||||
state = state.concat(c.slice(8, 12));
|
||||
state = state.concat(key.slice(16, 32));
|
||||
state = state.concat(c.slice(12, 16));
|
||||
|
||||
const x = Array();
|
||||
for (let i = 0; i < 64; i += 4) {
|
||||
x.push(Utils.byteArrayToInt(state.slice(i, i + 4), "little"));
|
||||
}
|
||||
const a = [...x];
|
||||
|
||||
salsa20Permute(x, rounds);
|
||||
|
||||
for (let i = 0; i < 16; i++) {
|
||||
x[i] = (x[i] + a[i]) & 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
let output = Array();
|
||||
for (let i = 0; i < 16; i++) {
|
||||
output = output.concat(Utils.intToByteArray(x[i], 4, "little"));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the hSalsa20 function
|
||||
*
|
||||
* @param {byteArray} key
|
||||
* @param {byteArray} nonce
|
||||
* @param {integer} rounds
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
export function hsalsa20(key, nonce, rounds) {
|
||||
const tau = "expand 16-byte k";
|
||||
const sigma = "expand 32-byte k";
|
||||
let state, c;
|
||||
if (key.length === 16) {
|
||||
c = Utils.strToByteArray(tau);
|
||||
key = key.concat(key);
|
||||
} else {
|
||||
c = Utils.strToByteArray(sigma);
|
||||
}
|
||||
|
||||
state = c.slice(0, 4);
|
||||
state = state.concat(key.slice(0, 16));
|
||||
state = state.concat(c.slice(4, 8));
|
||||
state = state.concat(nonce);
|
||||
state = state.concat(c.slice(8, 12));
|
||||
state = state.concat(key.slice(16, 32));
|
||||
state = state.concat(c.slice(12, 16));
|
||||
|
||||
const x = Array();
|
||||
for (let i = 0; i < 64; i += 4) {
|
||||
x.push(Utils.byteArrayToInt(state.slice(i, i + 4), "little"));
|
||||
}
|
||||
|
||||
salsa20Permute(x, rounds);
|
||||
|
||||
let output = Array();
|
||||
const idx = [0, 5, 10, 15, 6, 7, 8, 9];
|
||||
for (let i = 0; i < 8; i++) {
|
||||
output = output.concat(Utils.intToByteArray(x[idx[i]], 4, "little"));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
@@ -18,12 +18,23 @@ export default class Stream {
|
||||
* Stream constructor.
|
||||
*
|
||||
* @param {Uint8Array} input
|
||||
* @param {number} pos
|
||||
* @param {number} bitPos
|
||||
*/
|
||||
constructor(input) {
|
||||
constructor(input, pos=0, bitPos=0) {
|
||||
this.bytes = input;
|
||||
this.length = this.bytes.length;
|
||||
this.position = 0;
|
||||
this.bitPos = 0;
|
||||
this.position = pos;
|
||||
this.bitPos = bitPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone this Stream returning a new identical Stream.
|
||||
*
|
||||
* @returns {Stream}
|
||||
*/
|
||||
clone() {
|
||||
return new Stream(this.bytes, this.position, this.bitPos);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
877
src/core/lib/TLS.mjs
Normal file
877
src/core/lib/TLS.mjs
Normal file
@@ -0,0 +1,877 @@
|
||||
/**
|
||||
* TLS resources.
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Stream from "../lib/Stream.mjs";
|
||||
|
||||
/**
|
||||
* Parse a TLS Record
|
||||
* @param {Uint8Array} bytes
|
||||
* @returns {JSON}
|
||||
*/
|
||||
export function parseTLSRecord(bytes) {
|
||||
const s = new Stream(bytes);
|
||||
const b = s.clone();
|
||||
const r = {};
|
||||
|
||||
// Content type
|
||||
r.contentType = {
|
||||
description: "Content Type",
|
||||
length: 1,
|
||||
data: b.getBytes(1),
|
||||
value: s.readInt(1)
|
||||
};
|
||||
if (r.contentType.value !== 0x16)
|
||||
throw new OperationError("Not handshake data.");
|
||||
|
||||
// Version
|
||||
r.version = {
|
||||
description: "Protocol Version",
|
||||
length: 2,
|
||||
data: b.getBytes(2),
|
||||
value: s.readInt(2)
|
||||
};
|
||||
|
||||
// Length
|
||||
r.length = {
|
||||
description: "Record Length",
|
||||
length: 2,
|
||||
data: b.getBytes(2),
|
||||
value: s.readInt(2)
|
||||
};
|
||||
if (s.length !== r.length.value + 5)
|
||||
throw new OperationError("Incorrect handshake length.");
|
||||
|
||||
// Handshake
|
||||
r.handshake = {
|
||||
description: "Handshake",
|
||||
length: r.length.value,
|
||||
data: b.getBytes(r.length.value),
|
||||
value: parseHandshake(s.getBytes(r.length.value))
|
||||
};
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a TLS Handshake
|
||||
* @param {Uint8Array} bytes
|
||||
* @returns {JSON}
|
||||
*/
|
||||
function parseHandshake(bytes) {
|
||||
const s = new Stream(bytes);
|
||||
const b = s.clone();
|
||||
const h = {};
|
||||
|
||||
// Handshake type
|
||||
h.handshakeType = {
|
||||
description: "Handshake Type",
|
||||
length: 1,
|
||||
data: b.getBytes(1),
|
||||
value: s.readInt(1)
|
||||
};
|
||||
|
||||
// Handshake length
|
||||
h.handshakeLength = {
|
||||
description: "Handshake Length",
|
||||
length: 3,
|
||||
data: b.getBytes(3),
|
||||
value: s.readInt(3)
|
||||
};
|
||||
if (s.length !== h.handshakeLength.value + 4)
|
||||
throw new OperationError("Not enough data in Handshake message.");
|
||||
|
||||
|
||||
switch (h.handshakeType.value) {
|
||||
case 0x01:
|
||||
h.handshakeType.description = "Client Hello";
|
||||
parseClientHello(s, b, h);
|
||||
break;
|
||||
case 0x02:
|
||||
h.handshakeType.description = "Server Hello";
|
||||
parseServerHello(s, b, h);
|
||||
break;
|
||||
default:
|
||||
throw new OperationError("Not a known handshake message.");
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a TLS Client Hello
|
||||
* @param {Stream} s
|
||||
* @param {Stream} b
|
||||
* @param {Object} h
|
||||
* @returns {JSON}
|
||||
*/
|
||||
function parseClientHello(s, b, h) {
|
||||
// Hello version
|
||||
h.helloVersion = {
|
||||
description: "Client Hello Version",
|
||||
length: 2,
|
||||
data: b.getBytes(2),
|
||||
value: s.readInt(2)
|
||||
};
|
||||
|
||||
// Random
|
||||
h.random = {
|
||||
description: "Client Random",
|
||||
length: 32,
|
||||
data: b.getBytes(32),
|
||||
value: s.getBytes(32)
|
||||
};
|
||||
|
||||
// Session ID Length
|
||||
h.sessionIDLength = {
|
||||
description: "Session ID Length",
|
||||
length: 1,
|
||||
data: b.getBytes(1),
|
||||
value: s.readInt(1)
|
||||
};
|
||||
|
||||
// Session ID
|
||||
h.sessionID = {
|
||||
description: "Session ID",
|
||||
length: h.sessionIDLength.value,
|
||||
data: b.getBytes(h.sessionIDLength.value),
|
||||
value: s.getBytes(h.sessionIDLength.value)
|
||||
};
|
||||
|
||||
// Cipher Suites Length
|
||||
h.cipherSuitesLength = {
|
||||
description: "Cipher Suites Length",
|
||||
length: 2,
|
||||
data: b.getBytes(2),
|
||||
value: s.readInt(2)
|
||||
};
|
||||
|
||||
// Cipher Suites
|
||||
h.cipherSuites = {
|
||||
description: "Cipher Suites",
|
||||
length: h.cipherSuitesLength.value,
|
||||
data: b.getBytes(h.cipherSuitesLength.value),
|
||||
value: parseCipherSuites(s.getBytes(h.cipherSuitesLength.value))
|
||||
};
|
||||
|
||||
// Compression Methods Length
|
||||
h.compressionMethodsLength = {
|
||||
description: "Compression Methods Length",
|
||||
length: 1,
|
||||
data: b.getBytes(1),
|
||||
value: s.readInt(1)
|
||||
};
|
||||
|
||||
// Compression Methods
|
||||
h.compressionMethods = {
|
||||
description: "Compression Methods",
|
||||
length: h.compressionMethodsLength.value,
|
||||
data: b.getBytes(h.compressionMethodsLength.value),
|
||||
value: parseCompressionMethods(s.getBytes(h.compressionMethodsLength.value))
|
||||
};
|
||||
|
||||
// Extensions Length
|
||||
h.extensionsLength = {
|
||||
description: "Extensions Length",
|
||||
length: 2,
|
||||
data: b.getBytes(2),
|
||||
value: s.readInt(2)
|
||||
};
|
||||
|
||||
// Extensions
|
||||
h.extensions = {
|
||||
description: "Extensions",
|
||||
length: h.extensionsLength.value,
|
||||
data: b.getBytes(h.extensionsLength.value),
|
||||
value: parseExtensions(s.getBytes(h.extensionsLength.value))
|
||||
};
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a TLS Server Hello
|
||||
* @param {Stream} s
|
||||
* @param {Stream} b
|
||||
* @param {Object} h
|
||||
* @returns {JSON}
|
||||
*/
|
||||
function parseServerHello(s, b, h) {
|
||||
// Hello version
|
||||
h.helloVersion = {
|
||||
description: "Server Hello Version",
|
||||
length: 2,
|
||||
data: b.getBytes(2),
|
||||
value: s.readInt(2)
|
||||
};
|
||||
|
||||
// Random
|
||||
h.random = {
|
||||
description: "Server Random",
|
||||
length: 32,
|
||||
data: b.getBytes(32),
|
||||
value: s.getBytes(32)
|
||||
};
|
||||
|
||||
// Session ID Length
|
||||
h.sessionIDLength = {
|
||||
description: "Session ID Length",
|
||||
length: 1,
|
||||
data: b.getBytes(1),
|
||||
value: s.readInt(1)
|
||||
};
|
||||
|
||||
// Session ID
|
||||
h.sessionID = {
|
||||
description: "Session ID",
|
||||
length: h.sessionIDLength.value,
|
||||
data: b.getBytes(h.sessionIDLength.value),
|
||||
value: s.getBytes(h.sessionIDLength.value)
|
||||
};
|
||||
|
||||
// Cipher Suite
|
||||
h.cipherSuite = {
|
||||
description: "Selected Cipher Suite",
|
||||
length: 2,
|
||||
data: b.getBytes(2),
|
||||
value: CIPHER_SUITES_LOOKUP[s.readInt(2)] || "Unknown"
|
||||
};
|
||||
|
||||
// Compression Method
|
||||
h.compressionMethod = {
|
||||
description: "Selected Compression Method",
|
||||
length: 1,
|
||||
data: b.getBytes(1),
|
||||
value: s.readInt(1) // TODO: Compression method name here
|
||||
};
|
||||
|
||||
// Extensions Length
|
||||
h.extensionsLength = {
|
||||
description: "Extensions Length",
|
||||
length: 2,
|
||||
data: b.getBytes(2),
|
||||
value: s.readInt(2)
|
||||
};
|
||||
|
||||
// Extensions
|
||||
h.extensions = {
|
||||
description: "Extensions",
|
||||
length: h.extensionsLength.value,
|
||||
data: b.getBytes(h.extensionsLength.value),
|
||||
value: parseExtensions(s.getBytes(h.extensionsLength.value))
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse Cipher Suites
|
||||
* @param {Uint8Array} bytes
|
||||
* @returns {JSON}
|
||||
*/
|
||||
function parseCipherSuites(bytes) {
|
||||
const s = new Stream(bytes);
|
||||
const b = s.clone();
|
||||
const cs = [];
|
||||
|
||||
while (s.hasMore()) {
|
||||
cs.push({
|
||||
description: "Cipher Suite",
|
||||
length: 2,
|
||||
data: b.getBytes(2),
|
||||
value: CIPHER_SUITES_LOOKUP[s.readInt(2)] || "Unknown"
|
||||
});
|
||||
}
|
||||
return cs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse Compression Methods
|
||||
* @param {Uint8Array} bytes
|
||||
* @returns {JSON}
|
||||
*/
|
||||
function parseCompressionMethods(bytes) {
|
||||
const s = new Stream(bytes);
|
||||
const b = s.clone();
|
||||
const cm = [];
|
||||
|
||||
while (s.hasMore()) {
|
||||
cm.push({
|
||||
description: "Compression Method",
|
||||
length: 1,
|
||||
data: b.getBytes(1),
|
||||
value: s.readInt(1) // TODO: Compression method name here
|
||||
});
|
||||
}
|
||||
return cm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse Extensions
|
||||
* @param {Uint8Array} bytes
|
||||
* @returns {JSON}
|
||||
*/
|
||||
function parseExtensions(bytes) {
|
||||
const s = new Stream(bytes);
|
||||
const b = s.clone();
|
||||
|
||||
const exts = [];
|
||||
while (s.hasMore()) {
|
||||
const ext = {};
|
||||
|
||||
// Type
|
||||
ext.type = {
|
||||
description: "Extension Type",
|
||||
length: 2,
|
||||
data: b.getBytes(2),
|
||||
value: EXTENSION_LOOKUP[s.readInt(2)] || "unknown"
|
||||
};
|
||||
|
||||
// Length
|
||||
ext.length = {
|
||||
description: "Extension Length",
|
||||
length: 2,
|
||||
data: b.getBytes(2),
|
||||
value: s.readInt(2)
|
||||
};
|
||||
|
||||
// Value
|
||||
ext.value = {
|
||||
description: "Extension Value",
|
||||
length: ext.length.value,
|
||||
data: b.getBytes(ext.length.value),
|
||||
value: s.getBytes(ext.length.value)
|
||||
};
|
||||
|
||||
exts.push(ext);
|
||||
}
|
||||
|
||||
return exts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension type lookup table
|
||||
*/
|
||||
const EXTENSION_LOOKUP = {
|
||||
0: "server_name",
|
||||
1: "max_fragment_length",
|
||||
2: "client_certificate_url",
|
||||
3: "trusted_ca_keys",
|
||||
4: "truncated_hmac",
|
||||
5: "status_request",
|
||||
6: "user_mapping",
|
||||
7: "client_authz",
|
||||
8: "server_authz",
|
||||
9: "cert_type",
|
||||
10: "supported_groups",
|
||||
11: "ec_point_formats",
|
||||
12: "srp",
|
||||
13: "signature_algorithms",
|
||||
14: "use_srtp",
|
||||
15: "heartbeat",
|
||||
16: "application_layer_protocol_negotiation",
|
||||
17: "status_request_v2",
|
||||
18: "signed_certificate_timestamp",
|
||||
19: "client_certificate_type",
|
||||
20: "server_certificate_type",
|
||||
21: "padding",
|
||||
22: "encrypt_then_mac",
|
||||
23: "extended_master_secret",
|
||||
24: "token_binding",
|
||||
25: "cached_info",
|
||||
26: "tls_lts",
|
||||
27: "compress_certificate",
|
||||
28: "record_size_limit",
|
||||
29: "pwd_protect",
|
||||
30: "pwd_clear",
|
||||
31: "password_salt",
|
||||
32: "ticket_pinning",
|
||||
33: "tls_cert_with_extern_psk",
|
||||
34: "delegated_credential",
|
||||
35: "session_ticket",
|
||||
36: "TLMSP",
|
||||
37: "TLMSP_proxying",
|
||||
38: "TLMSP_delegate",
|
||||
39: "supported_ekt_ciphers",
|
||||
40: "Reserved",
|
||||
41: "pre_shared_key",
|
||||
42: "early_data",
|
||||
43: "supported_versions",
|
||||
44: "cookie",
|
||||
45: "psk_key_exchange_modes",
|
||||
46: "Reserved",
|
||||
47: "certificate_authorities",
|
||||
48: "oid_filters",
|
||||
49: "post_handshake_auth",
|
||||
50: "signature_algorithms_cert",
|
||||
51: "key_share",
|
||||
52: "transparency_info",
|
||||
53: "connection_id (deprecated)",
|
||||
54: "connection_id",
|
||||
55: "external_id_hash",
|
||||
56: "external_session_id",
|
||||
57: "quic_transport_parameters",
|
||||
58: "ticket_request",
|
||||
59: "dnssec_chain",
|
||||
60: "sequence_number_encryption_algorithms",
|
||||
61: "rrc",
|
||||
2570: "GREASE",
|
||||
6682: "GREASE",
|
||||
10794: "GREASE",
|
||||
14906: "GREASE",
|
||||
17513: "application_settings",
|
||||
19018: "GREASE",
|
||||
23130: "GREASE",
|
||||
27242: "GREASE",
|
||||
31354: "GREASE",
|
||||
35466: "GREASE",
|
||||
39578: "GREASE",
|
||||
43690: "GREASE",
|
||||
47802: "GREASE",
|
||||
51914: "GREASE",
|
||||
56026: "GREASE",
|
||||
60138: "GREASE",
|
||||
64250: "GREASE",
|
||||
64768: "ech_outer_extensions",
|
||||
65037: "encrypted_client_hello",
|
||||
65281: "renegotiation_info"
|
||||
};
|
||||
|
||||
/**
|
||||
* Cipher suites lookup table
|
||||
*/
|
||||
const CIPHER_SUITES_LOOKUP = {
|
||||
0x0000: "TLS_NULL_WITH_NULL_NULL",
|
||||
0x0001: "TLS_RSA_WITH_NULL_MD5",
|
||||
0x0002: "TLS_RSA_WITH_NULL_SHA",
|
||||
0x0003: "TLS_RSA_EXPORT_WITH_RC4_40_MD5",
|
||||
0x0004: "TLS_RSA_WITH_RC4_128_MD5",
|
||||
0x0005: "TLS_RSA_WITH_RC4_128_SHA",
|
||||
0x0006: "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5",
|
||||
0x0007: "TLS_RSA_WITH_IDEA_CBC_SHA",
|
||||
0x0008: "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA",
|
||||
0x0009: "TLS_RSA_WITH_DES_CBC_SHA",
|
||||
0x000A: "TLS_RSA_WITH_3DES_EDE_CBC_SHA",
|
||||
0x000B: "TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA",
|
||||
0x000C: "TLS_DH_DSS_WITH_DES_CBC_SHA",
|
||||
0x000D: "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA",
|
||||
0x000E: "TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA",
|
||||
0x000F: "TLS_DH_RSA_WITH_DES_CBC_SHA",
|
||||
0x0010: "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA",
|
||||
0x0011: "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA",
|
||||
0x0012: "TLS_DHE_DSS_WITH_DES_CBC_SHA",
|
||||
0x0013: "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
|
||||
0x0014: "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
|
||||
0x0015: "TLS_DHE_RSA_WITH_DES_CBC_SHA",
|
||||
0x0016: "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
|
||||
0x0017: "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5",
|
||||
0x0018: "TLS_DH_anon_WITH_RC4_128_MD5",
|
||||
0x0019: "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA",
|
||||
0x001A: "TLS_DH_anon_WITH_DES_CBC_SHA",
|
||||
0x001B: "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA",
|
||||
0x001E: "TLS_KRB5_WITH_DES_CBC_SHA",
|
||||
0x001F: "TLS_KRB5_WITH_3DES_EDE_CBC_SHA",
|
||||
0x0020: "TLS_KRB5_WITH_RC4_128_SHA",
|
||||
0x0021: "TLS_KRB5_WITH_IDEA_CBC_SHA",
|
||||
0x0022: "TLS_KRB5_WITH_DES_CBC_MD5",
|
||||
0x0023: "TLS_KRB5_WITH_3DES_EDE_CBC_MD5",
|
||||
0x0024: "TLS_KRB5_WITH_RC4_128_MD5",
|
||||
0x0025: "TLS_KRB5_WITH_IDEA_CBC_MD5",
|
||||
0x0026: "TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA",
|
||||
0x0027: "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA",
|
||||
0x0028: "TLS_KRB5_EXPORT_WITH_RC4_40_SHA",
|
||||
0x0029: "TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5",
|
||||
0x002A: "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5",
|
||||
0x002B: "TLS_KRB5_EXPORT_WITH_RC4_40_MD5",
|
||||
0x002C: "TLS_PSK_WITH_NULL_SHA",
|
||||
0x002D: "TLS_DHE_PSK_WITH_NULL_SHA",
|
||||
0x002E: "TLS_RSA_PSK_WITH_NULL_SHA",
|
||||
0x002F: "TLS_RSA_WITH_AES_128_CBC_SHA",
|
||||
0x0030: "TLS_DH_DSS_WITH_AES_128_CBC_SHA",
|
||||
0x0031: "TLS_DH_RSA_WITH_AES_128_CBC_SHA",
|
||||
0x0032: "TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
|
||||
0x0033: "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
|
||||
0x0034: "TLS_DH_anon_WITH_AES_128_CBC_SHA",
|
||||
0x0035: "TLS_RSA_WITH_AES_256_CBC_SHA",
|
||||
0x0036: "TLS_DH_DSS_WITH_AES_256_CBC_SHA",
|
||||
0x0037: "TLS_DH_RSA_WITH_AES_256_CBC_SHA",
|
||||
0x0038: "TLS_DHE_DSS_WITH_AES_256_CBC_SHA",
|
||||
0x0039: "TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
|
||||
0x003A: "TLS_DH_anon_WITH_AES_256_CBC_SHA",
|
||||
0x003B: "TLS_RSA_WITH_NULL_SHA256",
|
||||
0x003C: "TLS_RSA_WITH_AES_128_CBC_SHA256",
|
||||
0x003D: "TLS_RSA_WITH_AES_256_CBC_SHA256",
|
||||
0x003E: "TLS_DH_DSS_WITH_AES_128_CBC_SHA256",
|
||||
0x003F: "TLS_DH_RSA_WITH_AES_128_CBC_SHA256",
|
||||
0x0040: "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256",
|
||||
0x0041: "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA",
|
||||
0x0042: "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA",
|
||||
0x0043: "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA",
|
||||
0x0044: "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA",
|
||||
0x0045: "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA",
|
||||
0x0046: "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA",
|
||||
0x0067: "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",
|
||||
0x0068: "TLS_DH_DSS_WITH_AES_256_CBC_SHA256",
|
||||
0x0069: "TLS_DH_RSA_WITH_AES_256_CBC_SHA256",
|
||||
0x006A: "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256",
|
||||
0x006B: "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
|
||||
0x006C: "TLS_DH_anon_WITH_AES_128_CBC_SHA256",
|
||||
0x006D: "TLS_DH_anon_WITH_AES_256_CBC_SHA256",
|
||||
0x0084: "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA",
|
||||
0x0085: "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA",
|
||||
0x0086: "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA",
|
||||
0x0087: "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA",
|
||||
0x0088: "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA",
|
||||
0x0089: "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA",
|
||||
0x008A: "TLS_PSK_WITH_RC4_128_SHA",
|
||||
0x008B: "TLS_PSK_WITH_3DES_EDE_CBC_SHA",
|
||||
0x008C: "TLS_PSK_WITH_AES_128_CBC_SHA",
|
||||
0x008D: "TLS_PSK_WITH_AES_256_CBC_SHA",
|
||||
0x008E: "TLS_DHE_PSK_WITH_RC4_128_SHA",
|
||||
0x008F: "TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA",
|
||||
0x0090: "TLS_DHE_PSK_WITH_AES_128_CBC_SHA",
|
||||
0x0091: "TLS_DHE_PSK_WITH_AES_256_CBC_SHA",
|
||||
0x0092: "TLS_RSA_PSK_WITH_RC4_128_SHA",
|
||||
0x0093: "TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA",
|
||||
0x0094: "TLS_RSA_PSK_WITH_AES_128_CBC_SHA",
|
||||
0x0095: "TLS_RSA_PSK_WITH_AES_256_CBC_SHA",
|
||||
0x0096: "TLS_RSA_WITH_SEED_CBC_SHA",
|
||||
0x0097: "TLS_DH_DSS_WITH_SEED_CBC_SHA",
|
||||
0x0098: "TLS_DH_RSA_WITH_SEED_CBC_SHA",
|
||||
0x0099: "TLS_DHE_DSS_WITH_SEED_CBC_SHA",
|
||||
0x009A: "TLS_DHE_RSA_WITH_SEED_CBC_SHA",
|
||||
0x009B: "TLS_DH_anon_WITH_SEED_CBC_SHA",
|
||||
0x009C: "TLS_RSA_WITH_AES_128_GCM_SHA256",
|
||||
0x009D: "TLS_RSA_WITH_AES_256_GCM_SHA384",
|
||||
0x009E: "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||
0x009F: "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
|
||||
0x00A0: "TLS_DH_RSA_WITH_AES_128_GCM_SHA256",
|
||||
0x00A1: "TLS_DH_RSA_WITH_AES_256_GCM_SHA384",
|
||||
0x00A2: "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256",
|
||||
0x00A3: "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384",
|
||||
0x00A4: "TLS_DH_DSS_WITH_AES_128_GCM_SHA256",
|
||||
0x00A5: "TLS_DH_DSS_WITH_AES_256_GCM_SHA384",
|
||||
0x00A6: "TLS_DH_anon_WITH_AES_128_GCM_SHA256",
|
||||
0x00A7: "TLS_DH_anon_WITH_AES_256_GCM_SHA384",
|
||||
0x00A8: "TLS_PSK_WITH_AES_128_GCM_SHA256",
|
||||
0x00A9: "TLS_PSK_WITH_AES_256_GCM_SHA384",
|
||||
0x00AA: "TLS_DHE_PSK_WITH_AES_128_GCM_SHA256",
|
||||
0x00AB: "TLS_DHE_PSK_WITH_AES_256_GCM_SHA384",
|
||||
0x00AC: "TLS_RSA_PSK_WITH_AES_128_GCM_SHA256",
|
||||
0x00AD: "TLS_RSA_PSK_WITH_AES_256_GCM_SHA384",
|
||||
0x00AE: "TLS_PSK_WITH_AES_128_CBC_SHA256",
|
||||
0x00AF: "TLS_PSK_WITH_AES_256_CBC_SHA384",
|
||||
0x00B0: "TLS_PSK_WITH_NULL_SHA256",
|
||||
0x00B1: "TLS_PSK_WITH_NULL_SHA384",
|
||||
0x00B2: "TLS_DHE_PSK_WITH_AES_128_CBC_SHA256",
|
||||
0x00B3: "TLS_DHE_PSK_WITH_AES_256_CBC_SHA384",
|
||||
0x00B4: "TLS_DHE_PSK_WITH_NULL_SHA256",
|
||||
0x00B5: "TLS_DHE_PSK_WITH_NULL_SHA384",
|
||||
0x00B6: "TLS_RSA_PSK_WITH_AES_128_CBC_SHA256",
|
||||
0x00B7: "TLS_RSA_PSK_WITH_AES_256_CBC_SHA384",
|
||||
0x00B8: "TLS_RSA_PSK_WITH_NULL_SHA256",
|
||||
0x00B9: "TLS_RSA_PSK_WITH_NULL_SHA384",
|
||||
0x00BA: "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256",
|
||||
0x00BB: "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256",
|
||||
0x00BC: "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256",
|
||||
0x00BD: "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256",
|
||||
0x00BE: "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256",
|
||||
0x00BF: "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256",
|
||||
0x00C0: "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256",
|
||||
0x00C1: "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256",
|
||||
0x00C2: "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256",
|
||||
0x00C3: "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256",
|
||||
0x00C4: "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256",
|
||||
0x00C5: "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256",
|
||||
0x00C6: "TLS_SM4_GCM_SM3",
|
||||
0x00C7: "TLS_SM4_CCM_SM3",
|
||||
0x00FF: "TLS_EMPTY_RENEGOTIATION_INFO_SCSV",
|
||||
0x0A0A: "GREASE",
|
||||
0x1301: "TLS_AES_128_GCM_SHA256",
|
||||
0x1302: "TLS_AES_256_GCM_SHA384",
|
||||
0x1303: "TLS_CHACHA20_POLY1305_SHA256",
|
||||
0x1304: "TLS_AES_128_CCM_SHA256",
|
||||
0x1305: "TLS_AES_128_CCM_8_SHA256",
|
||||
0x1306: "TLS_AEGIS_256_SHA512",
|
||||
0x1307: "TLS_AEGIS_128L_SHA256",
|
||||
0x1A1A: "GREASE",
|
||||
0x2A2A: "GREASE",
|
||||
0x3A3A: "GREASE",
|
||||
0x4A4A: "GREASE",
|
||||
0x5600: "TLS_FALLBACK_SCSV",
|
||||
0x5A5A: "GREASE",
|
||||
0x6A6A: "GREASE",
|
||||
0x7A7A: "GREASE",
|
||||
0x8A8A: "GREASE",
|
||||
0x9A9A: "GREASE",
|
||||
0xAAAA: "GREASE",
|
||||
0xBABA: "GREASE",
|
||||
0xC001: "TLS_ECDH_ECDSA_WITH_NULL_SHA",
|
||||
0xC002: "TLS_ECDH_ECDSA_WITH_RC4_128_SHA",
|
||||
0xC003: "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA",
|
||||
0xC004: "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA",
|
||||
0xC005: "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA",
|
||||
0xC006: "TLS_ECDHE_ECDSA_WITH_NULL_SHA",
|
||||
0xC007: "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
|
||||
0xC008: "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
|
||||
0xC009: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
|
||||
0xC00A: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
|
||||
0xC00B: "TLS_ECDH_RSA_WITH_NULL_SHA",
|
||||
0xC00C: "TLS_ECDH_RSA_WITH_RC4_128_SHA",
|
||||
0xC00D: "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA",
|
||||
0xC00E: "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA",
|
||||
0xC00F: "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA",
|
||||
0xC010: "TLS_ECDHE_RSA_WITH_NULL_SHA",
|
||||
0xC011: "TLS_ECDHE_RSA_WITH_RC4_128_SHA",
|
||||
0xC012: "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
|
||||
0xC013: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
|
||||
0xC014: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
|
||||
0xC015: "TLS_ECDH_anon_WITH_NULL_SHA",
|
||||
0xC016: "TLS_ECDH_anon_WITH_RC4_128_SHA",
|
||||
0xC017: "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA",
|
||||
0xC018: "TLS_ECDH_anon_WITH_AES_128_CBC_SHA",
|
||||
0xC019: "TLS_ECDH_anon_WITH_AES_256_CBC_SHA",
|
||||
0xC01A: "TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA",
|
||||
0xC01B: "TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA",
|
||||
0xC01C: "TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA",
|
||||
0xC01D: "TLS_SRP_SHA_WITH_AES_128_CBC_SHA",
|
||||
0xC01E: "TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA",
|
||||
0xC01F: "TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA",
|
||||
0xC020: "TLS_SRP_SHA_WITH_AES_256_CBC_SHA",
|
||||
0xC021: "TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA",
|
||||
0xC022: "TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA",
|
||||
0xC023: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
|
||||
0xC024: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
|
||||
0xC025: "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256",
|
||||
0xC026: "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384",
|
||||
0xC027: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
|
||||
0xC028: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
|
||||
0xC029: "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256",
|
||||
0xC02A: "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384",
|
||||
0xC02B: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
||||
0xC02C: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
|
||||
0xC02D: "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256",
|
||||
0xC02E: "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384",
|
||||
0xC02F: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||
0xC030: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
|
||||
0xC031: "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256",
|
||||
0xC032: "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384",
|
||||
0xC033: "TLS_ECDHE_PSK_WITH_RC4_128_SHA",
|
||||
0xC034: "TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA",
|
||||
0xC035: "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA",
|
||||
0xC036: "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA",
|
||||
0xC037: "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256",
|
||||
0xC038: "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384",
|
||||
0xC039: "TLS_ECDHE_PSK_WITH_NULL_SHA",
|
||||
0xC03A: "TLS_ECDHE_PSK_WITH_NULL_SHA256",
|
||||
0xC03B: "TLS_ECDHE_PSK_WITH_NULL_SHA384",
|
||||
0xC03C: "TLS_RSA_WITH_ARIA_128_CBC_SHA256",
|
||||
0xC03D: "TLS_RSA_WITH_ARIA_256_CBC_SHA384",
|
||||
0xC03E: "TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256",
|
||||
0xC03F: "TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384",
|
||||
0xC040: "TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256",
|
||||
0xC041: "TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384",
|
||||
0xC042: "TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256",
|
||||
0xC043: "TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384",
|
||||
0xC044: "TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256",
|
||||
0xC045: "TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384",
|
||||
0xC046: "TLS_DH_anon_WITH_ARIA_128_CBC_SHA256",
|
||||
0xC047: "TLS_DH_anon_WITH_ARIA_256_CBC_SHA384",
|
||||
0xC048: "TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256",
|
||||
0xC049: "TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384",
|
||||
0xC04A: "TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256",
|
||||
0xC04B: "TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384",
|
||||
0xC04C: "TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256",
|
||||
0xC04D: "TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384",
|
||||
0xC04E: "TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256",
|
||||
0xC04F: "TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384",
|
||||
0xC050: "TLS_RSA_WITH_ARIA_128_GCM_SHA256",
|
||||
0xC051: "TLS_RSA_WITH_ARIA_256_GCM_SHA384",
|
||||
0xC052: "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256",
|
||||
0xC053: "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384",
|
||||
0xC054: "TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256",
|
||||
0xC055: "TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384",
|
||||
0xC056: "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256",
|
||||
0xC057: "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384",
|
||||
0xC058: "TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256",
|
||||
0xC059: "TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384",
|
||||
0xC05A: "TLS_DH_anon_WITH_ARIA_128_GCM_SHA256",
|
||||
0xC05B: "TLS_DH_anon_WITH_ARIA_256_GCM_SHA384",
|
||||
0xC05C: "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256",
|
||||
0xC05D: "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384",
|
||||
0xC05E: "TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256",
|
||||
0xC05F: "TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384",
|
||||
0xC060: "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256",
|
||||
0xC061: "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384",
|
||||
0xC062: "TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256",
|
||||
0xC063: "TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384",
|
||||
0xC064: "TLS_PSK_WITH_ARIA_128_CBC_SHA256",
|
||||
0xC065: "TLS_PSK_WITH_ARIA_256_CBC_SHA384",
|
||||
0xC066: "TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256",
|
||||
0xC067: "TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384",
|
||||
0xC068: "TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256",
|
||||
0xC069: "TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384",
|
||||
0xC06A: "TLS_PSK_WITH_ARIA_128_GCM_SHA256",
|
||||
0xC06B: "TLS_PSK_WITH_ARIA_256_GCM_SHA384",
|
||||
0xC06C: "TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256",
|
||||
0xC06D: "TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384",
|
||||
0xC06E: "TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256",
|
||||
0xC06F: "TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384",
|
||||
0xC070: "TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256",
|
||||
0xC071: "TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384",
|
||||
0xC072: "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256",
|
||||
0xC073: "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384",
|
||||
0xC074: "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256",
|
||||
0xC075: "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384",
|
||||
0xC076: "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256",
|
||||
0xC077: "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384",
|
||||
0xC078: "TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256",
|
||||
0xC079: "TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384",
|
||||
0xC07A: "TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256",
|
||||
0xC07B: "TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384",
|
||||
0xC07C: "TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256",
|
||||
0xC07D: "TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384",
|
||||
0xC07E: "TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256",
|
||||
0xC07F: "TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384",
|
||||
0xC080: "TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256",
|
||||
0xC081: "TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384",
|
||||
0xC082: "TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256",
|
||||
0xC083: "TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384",
|
||||
0xC084: "TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256",
|
||||
0xC085: "TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384",
|
||||
0xC086: "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256",
|
||||
0xC087: "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384",
|
||||
0xC088: "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256",
|
||||
0xC089: "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384",
|
||||
0xC08A: "TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256",
|
||||
0xC08B: "TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384",
|
||||
0xC08C: "TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256",
|
||||
0xC08D: "TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384",
|
||||
0xC08E: "TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256",
|
||||
0xC08F: "TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384",
|
||||
0xC090: "TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256",
|
||||
0xC091: "TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384",
|
||||
0xC092: "TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256",
|
||||
0xC093: "TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384",
|
||||
0xC094: "TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256",
|
||||
0xC095: "TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384",
|
||||
0xC096: "TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256",
|
||||
0xC097: "TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384",
|
||||
0xC098: "TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256",
|
||||
0xC099: "TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384",
|
||||
0xC09A: "TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256",
|
||||
0xC09B: "TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384",
|
||||
0xC09C: "TLS_RSA_WITH_AES_128_CCM",
|
||||
0xC09D: "TLS_RSA_WITH_AES_256_CCM",
|
||||
0xC09E: "TLS_DHE_RSA_WITH_AES_128_CCM",
|
||||
0xC09F: "TLS_DHE_RSA_WITH_AES_256_CCM",
|
||||
0xC0A0: "TLS_RSA_WITH_AES_128_CCM_8",
|
||||
0xC0A1: "TLS_RSA_WITH_AES_256_CCM_8",
|
||||
0xC0A2: "TLS_DHE_RSA_WITH_AES_128_CCM_8",
|
||||
0xC0A3: "TLS_DHE_RSA_WITH_AES_256_CCM_8",
|
||||
0xC0A4: "TLS_PSK_WITH_AES_128_CCM",
|
||||
0xC0A5: "TLS_PSK_WITH_AES_256_CCM",
|
||||
0xC0A6: "TLS_DHE_PSK_WITH_AES_128_CCM",
|
||||
0xC0A7: "TLS_DHE_PSK_WITH_AES_256_CCM",
|
||||
0xC0A8: "TLS_PSK_WITH_AES_128_CCM_8",
|
||||
0xC0A9: "TLS_PSK_WITH_AES_256_CCM_8",
|
||||
0xC0AA: "TLS_PSK_DHE_WITH_AES_128_CCM_8",
|
||||
0xC0AB: "TLS_PSK_DHE_WITH_AES_256_CCM_8",
|
||||
0xC0AC: "TLS_ECDHE_ECDSA_WITH_AES_128_CCM",
|
||||
0xC0AD: "TLS_ECDHE_ECDSA_WITH_AES_256_CCM",
|
||||
0xC0AE: "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8",
|
||||
0xC0AF: "TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8",
|
||||
0xC0B0: "TLS_ECCPWD_WITH_AES_128_GCM_SHA256",
|
||||
0xC0B1: "TLS_ECCPWD_WITH_AES_256_GCM_SHA384",
|
||||
0xC0B2: "TLS_ECCPWD_WITH_AES_128_CCM_SHA256",
|
||||
0xC0B3: "TLS_ECCPWD_WITH_AES_256_CCM_SHA384",
|
||||
0xC0B4: "TLS_SHA256_SHA256",
|
||||
0xC0B5: "TLS_SHA384_SHA384",
|
||||
0xC100: "TLS_GOSTR341112_256_WITH_KUZNYECHIK_CTR_OMAC",
|
||||
0xC101: "TLS_GOSTR341112_256_WITH_MAGMA_CTR_OMAC",
|
||||
0xC102: "TLS_GOSTR341112_256_WITH_28147_CNT_IMIT",
|
||||
0xC103: "TLS_GOSTR341112_256_WITH_KUZNYECHIK_MGM_L",
|
||||
0xC104: "TLS_GOSTR341112_256_WITH_MAGMA_MGM_L",
|
||||
0xC105: "TLS_GOSTR341112_256_WITH_KUZNYECHIK_MGM_S",
|
||||
0xC106: "TLS_GOSTR341112_256_WITH_MAGMA_MGM_S",
|
||||
0xCACA: "GREASE",
|
||||
0xCCA8: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
|
||||
0xCCA9: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
|
||||
0xCCAA: "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
|
||||
0xCCAB: "TLS_PSK_WITH_CHACHA20_POLY1305_SHA256",
|
||||
0xCCAC: "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256",
|
||||
0xCCAD: "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256",
|
||||
0xCCAE: "TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256",
|
||||
0xD001: "TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256",
|
||||
0xD002: "TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384",
|
||||
0xD003: "TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256",
|
||||
0xD005: "TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256",
|
||||
0xDADA: "GREASE",
|
||||
0xEAEA: "GREASE",
|
||||
0xFAFA: "GREASE",
|
||||
};
|
||||
|
||||
/**
|
||||
* GREASE values
|
||||
*/
|
||||
export const GREASE_VALUES = [
|
||||
0x0a0a,
|
||||
0x1a1a,
|
||||
0x2a2a,
|
||||
0x3a3a,
|
||||
0x4a4a,
|
||||
0x5a5a,
|
||||
0x6a6a,
|
||||
0x7a7a,
|
||||
0x8a8a,
|
||||
0x9a9a,
|
||||
0xaaaa,
|
||||
0xbaba,
|
||||
0xcaca,
|
||||
0xdada,
|
||||
0xeaea,
|
||||
0xfafa
|
||||
];
|
||||
|
||||
/**
|
||||
* Parses the supported_versions extension and returns the highest supported version.
|
||||
* @param {Uint8Array} bytes
|
||||
* @returns {number}
|
||||
*/
|
||||
export function parseHighestSupportedVersion(bytes) {
|
||||
const s = new Stream(bytes);
|
||||
|
||||
// The Server Hello supported_versions extension simply contains the chosen version
|
||||
if (s.length === 2) {
|
||||
return s.readInt(2);
|
||||
}
|
||||
|
||||
// Length
|
||||
let i = s.readInt(1);
|
||||
|
||||
let highestVersion = 0;
|
||||
while (s.hasMore() && i-- > 0) {
|
||||
const v = s.readInt(2);
|
||||
if (GREASE_VALUES.includes(v)) continue;
|
||||
if (v > highestVersion) highestVersion = v;
|
||||
}
|
||||
|
||||
return highestVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the application_layer_protocol_negotiation extension and returns the first value.
|
||||
* @param {Uint8Array} bytes
|
||||
* @returns {number}
|
||||
*/
|
||||
export function parseFirstALPNValue(bytes) {
|
||||
const s = new Stream(bytes);
|
||||
const alpnExtLen = s.readInt(2);
|
||||
if (alpnExtLen < 3) return "00";
|
||||
const strLen = s.readInt(1);
|
||||
if (strLen < 2) return "00";
|
||||
return s.readString(strLen);
|
||||
}
|
||||
174
src/core/lib/XXTEA.mjs
Normal file
174
src/core/lib/XXTEA.mjs
Normal file
@@ -0,0 +1,174 @@
|
||||
/**
|
||||
* XXTEA library
|
||||
*
|
||||
* Encryption Algorithm Authors:
|
||||
* David J. Wheeler
|
||||
* Roger M. Needham
|
||||
*
|
||||
* @author Ma Bingyao [mabingyao@gmail.com]
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
const DELTA = 0x9E3779B9;
|
||||
|
||||
/**
|
||||
* Convert a buffer to a Uint8Array
|
||||
* @param {Uint32Array} v
|
||||
* @param {boolean} includeLength
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
function toUint8Array(v, includeLength) {
|
||||
const length = v.length;
|
||||
let n = length << 2;
|
||||
if (includeLength) {
|
||||
const m = v[length - 1];
|
||||
n -= 4;
|
||||
if ((m < n - 3) || (m > n)) {
|
||||
return null;
|
||||
}
|
||||
n = m;
|
||||
}
|
||||
const bytes = new Uint8Array(n);
|
||||
for (let i = 0; i < n; i++) {
|
||||
bytes[i] = v[i >> 2] >> ((i & 3) << 3);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a buffer to a Uint32Array
|
||||
* @param {TypedArray} bs
|
||||
* @param {boolean} includeLength
|
||||
* @returns {Uint32Array}
|
||||
*/
|
||||
function toUint32Array(bs, includeLength) {
|
||||
const length = bs.length;
|
||||
let n = length >> 2;
|
||||
if ((length & 3) !== 0) {
|
||||
++n;
|
||||
}
|
||||
let v;
|
||||
if (includeLength) {
|
||||
v = new Uint32Array(n + 1);
|
||||
v[n] = length;
|
||||
} else {
|
||||
v = new Uint32Array(n);
|
||||
}
|
||||
for (let i = 0; i < length; ++i) {
|
||||
v[i >> 2] |= bs[i] << ((i & 3) << 3);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mask an int to 32 bits
|
||||
* @param {number} i
|
||||
* @returns {number}
|
||||
*/
|
||||
function int32(i) {
|
||||
return i & 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* MX function for data randomisation
|
||||
* @param {number} sum
|
||||
* @param {number} y
|
||||
* @param {number} z
|
||||
* @param {number} p
|
||||
* @param {number} e
|
||||
* @param {number} k
|
||||
* @returns {number}
|
||||
*/
|
||||
function mx(sum, y, z, p, e, k) {
|
||||
return ((z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4)) ^ ((sum ^ y) + (k[p & 3 ^ e] ^ z));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure an array is a multiple of 16 bits
|
||||
* @param {TypedArray} k
|
||||
* @returns {TypedArray}
|
||||
*/
|
||||
function fixk(k) {
|
||||
if (k.length < 16) {
|
||||
const key = new Uint8Array(16);
|
||||
key.set(k);
|
||||
return key;
|
||||
}
|
||||
return k;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs XXTEA encryption on a Uint32Array
|
||||
* @param {Uint32Array} v
|
||||
* @param {Uint32Array} k
|
||||
* @returns {Uint32Array}
|
||||
*/
|
||||
function encryptUint32Array(v, k) {
|
||||
const length = v.length;
|
||||
const n = length - 1;
|
||||
let y, z, sum, e, p, q;
|
||||
z = v[n];
|
||||
sum = 0;
|
||||
for (q = Math.floor(6 + 52 / length) | 0; q > 0; --q) {
|
||||
sum = int32(sum + DELTA);
|
||||
e = sum >>> 2 & 3;
|
||||
for (p = 0; p < n; ++p) {
|
||||
y = v[p + 1];
|
||||
z = v[p] = int32(v[p] + mx(sum, y, z, p, e, k));
|
||||
}
|
||||
y = v[0];
|
||||
z = v[n] = int32(v[n] + mx(sum, y, z, n, e, k));
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs XXTEA decryption on a Uint32Array
|
||||
* @param {Uint32Array} v
|
||||
* @param {Uint32Array} k
|
||||
* @returns {Uint32Array}
|
||||
*/
|
||||
function decryptUint32Array(v, k) {
|
||||
const length = v.length;
|
||||
const n = length - 1;
|
||||
let y, z, sum, e, p;
|
||||
y = v[0];
|
||||
const q = Math.floor(6 + 52 / length);
|
||||
for (sum = int32(q * DELTA); sum !== 0; sum = int32(sum - DELTA)) {
|
||||
e = sum >>> 2 & 3;
|
||||
for (p = n; p > 0; --p) {
|
||||
z = v[p - 1];
|
||||
y = v[p] = int32(v[p] - mx(sum, y, z, p, e, k));
|
||||
}
|
||||
z = v[n];
|
||||
y = v[0] = int32(v[0] - mx(sum, y, z, 0, e, k));
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt function
|
||||
* @param {TypedArray} data
|
||||
* @param {TypedArray} key
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
export function encrypt(data, key) {
|
||||
if (data === undefined || data === null || data.length === 0) {
|
||||
return data;
|
||||
}
|
||||
return toUint8Array(encryptUint32Array(toUint32Array(data, true), toUint32Array(fixk(key), false)), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt function
|
||||
* @param {TypedArray} data
|
||||
* @param {TypedArray} key
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
export function decrypt(data, key) {
|
||||
if (data === undefined || data === null || data.length === 0) {
|
||||
return data;
|
||||
}
|
||||
return toUint8Array(decryptUint32Array(toUint32Array(data, false), toUint32Array(fixk(key), false)), true);
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Add Text To Image operation
|
||||
@@ -127,7 +127,7 @@ class AddTextToImage extends Operation {
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
@@ -163,7 +163,7 @@ class AddTextToImage extends Operation {
|
||||
const font = fontsMap[fontFace];
|
||||
|
||||
// LoadFont needs an absolute url, so append the font name to self.docURL
|
||||
const jimpFont = await jimp.loadFont(self.docURL + "/" + font.default);
|
||||
const jimpFont = await Jimp.loadFont(self.docURL + "/" + font.default);
|
||||
|
||||
jimpFont.pages.forEach(function(page) {
|
||||
if (page.bitmap) {
|
||||
@@ -190,7 +190,7 @@ class AddTextToImage extends Operation {
|
||||
});
|
||||
|
||||
// Create a temporary image to hold the rendered text
|
||||
const textImage = new jimp(jimp.measureText(jimpFont, text), jimp.measureTextHeight(jimpFont, text));
|
||||
const textImage = new Jimp(Jimp.measureText(jimpFont, text), Jimp.measureTextHeight(jimpFont, text));
|
||||
textImage.print(jimpFont, 0, 0, text);
|
||||
|
||||
// Scale the rendered text image to the correct size
|
||||
@@ -198,9 +198,9 @@ class AddTextToImage extends Operation {
|
||||
if (size !== 1) {
|
||||
// Use bicubic for decreasing size
|
||||
if (size > 1) {
|
||||
textImage.scale(scaleFactor, jimp.RESIZE_BICUBIC);
|
||||
textImage.scale(scaleFactor, Jimp.RESIZE_BICUBIC);
|
||||
} else {
|
||||
textImage.scale(scaleFactor, jimp.RESIZE_BILINEAR);
|
||||
textImage.scale(scaleFactor, Jimp.RESIZE_BILINEAR);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,9 +234,9 @@ class AddTextToImage extends Operation {
|
||||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
||||
@@ -70,10 +70,14 @@ class BlowfishDecrypt extends Operation {
|
||||
inputType = args[3],
|
||||
outputType = args[4];
|
||||
|
||||
if (key.length !== 8) {
|
||||
if (key.length < 4 || key.length > 56) {
|
||||
throw new OperationError(`Invalid key length: ${key.length} bytes
|
||||
|
||||
Blowfish uses a key length of 8 bytes (64 bits).`);
|
||||
Blowfish's key length needs to be between 4 and 56 bytes (32-448 bits).`);
|
||||
}
|
||||
|
||||
if (iv.length !== 8) {
|
||||
throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes`);
|
||||
}
|
||||
|
||||
input = Utils.convertToByteString(input, inputType);
|
||||
|
||||
@@ -70,10 +70,14 @@ class BlowfishEncrypt extends Operation {
|
||||
inputType = args[3],
|
||||
outputType = args[4];
|
||||
|
||||
if (key.length !== 8) {
|
||||
if (key.length < 4 || key.length > 56) {
|
||||
throw new OperationError(`Invalid key length: ${key.length} bytes
|
||||
|
||||
Blowfish's key length needs to be between 4 and 56 bytes (32-448 bits).`);
|
||||
}
|
||||
|
||||
Blowfish uses a key length of 8 bytes (64 bits).`);
|
||||
if (iv.length !== 8) {
|
||||
throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes`);
|
||||
}
|
||||
|
||||
input = Utils.convertToByteString(input, inputType);
|
||||
|
||||
@@ -10,7 +10,7 @@ import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { gaussianBlur } from "../lib/ImageManipulation.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Blur Image operation
|
||||
@@ -59,7 +59,7 @@ class BlurImage extends Operation {
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
@@ -79,9 +79,9 @@ class BlurImage extends Operation {
|
||||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import xmldom from "xmldom";
|
||||
import xmldom from "@xmldom/xmldom";
|
||||
import nwmatcher from "nwmatcher";
|
||||
|
||||
/**
|
||||
|
||||
98
src/core/operations/CaretMdecode.mjs
Normal file
98
src/core/operations/CaretMdecode.mjs
Normal file
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* @author tedk [tedk@ted.do]
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
|
||||
/**
|
||||
* Caret/M-decode operation
|
||||
*
|
||||
* https://gist.githubusercontent.com/JaHIY/3c91bbf7bea5661e6abfbd1349ee81a2/raw/c7b480e9ff24bcb8f5287a8a8a2dcb9bf5628506/decode_m_notation.cpp
|
||||
*/
|
||||
class CaretMdecode extends Operation {
|
||||
|
||||
/**
|
||||
* CaretMdecode constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Caret/M-decode";
|
||||
this.module = "Default";
|
||||
this.description = "Decodes caret or M-encoded strings, i.e. ^M turns into a newline, M-^] turns into 0x9d. Sources such as `cat -v`.\n\nPlease be aware that when using `cat -v` ^_ (caret-underscore) will not be encoded, but represents a valid encoding (namely that of 0x1f).";
|
||||
this.infoURL = "https://en.wikipedia.org/wiki/Caret_notation";
|
||||
this.inputType = "string";
|
||||
this.outputType = "byteArray";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
run(input, args) {
|
||||
|
||||
const bytes = [];
|
||||
|
||||
let prev = "";
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
|
||||
const charCode = input.charCodeAt(i);
|
||||
const curChar = input.charAt(i);
|
||||
|
||||
if (prev === "M-^") {
|
||||
if (charCode > 63 && charCode <= 95) {
|
||||
bytes.push(charCode + 64);
|
||||
} else if (charCode === 63) {
|
||||
bytes.push(255);
|
||||
} else {
|
||||
bytes.push(77, 45, 94, charCode);
|
||||
}
|
||||
prev = "";
|
||||
} else if (prev === "M-") {
|
||||
if (curChar === "^") {
|
||||
prev = prev + "^";
|
||||
} else if (charCode >= 32 && charCode <= 126) {
|
||||
bytes.push(charCode + 128);
|
||||
prev = "";
|
||||
} else {
|
||||
bytes.push(77, 45, charCode);
|
||||
prev = "";
|
||||
}
|
||||
} else if (prev === "M") {
|
||||
if (curChar === "-") {
|
||||
prev = prev + "-";
|
||||
} else {
|
||||
bytes.push(77, charCode);
|
||||
prev = "";
|
||||
}
|
||||
} else if (prev === "^") {
|
||||
if (charCode > 63 && charCode <= 126) {
|
||||
bytes.push(charCode - 64);
|
||||
} else if (charCode === 63) {
|
||||
bytes.push(127);
|
||||
} else {
|
||||
bytes.push(94, charCode);
|
||||
}
|
||||
prev = "";
|
||||
} else {
|
||||
if (curChar === "M") {
|
||||
prev = "M";
|
||||
} else if (curChar === "^") {
|
||||
prev = "^";
|
||||
} else {
|
||||
bytes.push(charCode);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CaretMdecode;
|
||||
@@ -100,7 +100,7 @@ class ChaCha extends Operation {
|
||||
super();
|
||||
|
||||
this.name = "ChaCha";
|
||||
this.module = "Default";
|
||||
this.module = "Ciphers";
|
||||
this.description = "ChaCha is a stream cipher designed by Daniel J. Bernstein. It is a variant of the Salsa stream cipher. Several parameterizations exist; 'ChaCha' may refer to the original construction, or to the variant as described in RFC-8439. ChaCha is often used with Poly1305, in the ChaCha20-Poly1305 AEAD construction.<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";
|
||||
@@ -191,7 +191,7 @@ ChaCha uses a nonce of 8 or 12 bytes (64 or 96 bits).`);
|
||||
if (outputType === "Hex") {
|
||||
return toHex(output);
|
||||
} else {
|
||||
return Utils.arrayBufferToStr(output);
|
||||
return Utils.arrayBufferToStr(Uint8Array.from(output).buffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Contain Image operation
|
||||
@@ -91,20 +91,20 @@ class ContainImage extends Operation {
|
||||
const [width, height, hAlign, vAlign, alg, opaqueBg] = args;
|
||||
|
||||
const resizeMap = {
|
||||
"Nearest Neighbour": jimp.RESIZE_NEAREST_NEIGHBOR,
|
||||
"Bilinear": jimp.RESIZE_BILINEAR,
|
||||
"Bicubic": jimp.RESIZE_BICUBIC,
|
||||
"Hermite": jimp.RESIZE_HERMITE,
|
||||
"Bezier": jimp.RESIZE_BEZIER
|
||||
"Nearest Neighbour": Jimp.RESIZE_NEAREST_NEIGHBOR,
|
||||
"Bilinear": Jimp.RESIZE_BILINEAR,
|
||||
"Bicubic": Jimp.RESIZE_BICUBIC,
|
||||
"Hermite": Jimp.RESIZE_HERMITE,
|
||||
"Bezier": Jimp.RESIZE_BEZIER
|
||||
};
|
||||
|
||||
const alignMap = {
|
||||
"Left": jimp.HORIZONTAL_ALIGN_LEFT,
|
||||
"Center": jimp.HORIZONTAL_ALIGN_CENTER,
|
||||
"Right": jimp.HORIZONTAL_ALIGN_RIGHT,
|
||||
"Top": jimp.VERTICAL_ALIGN_TOP,
|
||||
"Middle": jimp.VERTICAL_ALIGN_MIDDLE,
|
||||
"Bottom": jimp.VERTICAL_ALIGN_BOTTOM
|
||||
"Left": Jimp.HORIZONTAL_ALIGN_LEFT,
|
||||
"Center": Jimp.HORIZONTAL_ALIGN_CENTER,
|
||||
"Right": Jimp.HORIZONTAL_ALIGN_RIGHT,
|
||||
"Top": Jimp.VERTICAL_ALIGN_TOP,
|
||||
"Middle": Jimp.VERTICAL_ALIGN_MIDDLE,
|
||||
"Bottom": Jimp.VERTICAL_ALIGN_BOTTOM
|
||||
};
|
||||
|
||||
if (!isImage(input)) {
|
||||
@@ -113,7 +113,7 @@ class ContainImage extends Operation {
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
@@ -123,16 +123,16 @@ class ContainImage extends Operation {
|
||||
image.contain(width, height, alignMap[hAlign] | alignMap[vAlign], resizeMap[alg]);
|
||||
|
||||
if (opaqueBg) {
|
||||
const newImage = await jimp.read(width, height, 0x000000FF);
|
||||
const newImage = await Jimp.read(width, height, 0x000000FF);
|
||||
newImage.blit(image, 0, 0);
|
||||
image = newImage;
|
||||
}
|
||||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Convert Image Format operation
|
||||
@@ -76,19 +76,19 @@ class ConvertImageFormat extends Operation {
|
||||
async run(input, args) {
|
||||
const [format, jpegQuality, pngFilterType, pngDeflateLevel] = args;
|
||||
const formatMap = {
|
||||
"JPEG": jimp.MIME_JPEG,
|
||||
"PNG": jimp.MIME_PNG,
|
||||
"BMP": jimp.MIME_BMP,
|
||||
"TIFF": jimp.MIME_TIFF
|
||||
"JPEG": Jimp.MIME_JPEG,
|
||||
"PNG": Jimp.MIME_PNG,
|
||||
"BMP": Jimp.MIME_BMP,
|
||||
"TIFF": Jimp.MIME_TIFF
|
||||
};
|
||||
|
||||
const pngFilterMap = {
|
||||
"Auto": jimp.PNG_FILTER_AUTO,
|
||||
"None": jimp.PNG_FILTER_NONE,
|
||||
"Sub": jimp.PNG_FILTER_SUB,
|
||||
"Up": jimp.PNG_FILTER_UP,
|
||||
"Average": jimp.PNG_FILTER_AVERAGE,
|
||||
"Paeth": jimp.PNG_FILTER_PATH
|
||||
"Auto": Jimp.PNG_FILTER_AUTO,
|
||||
"None": Jimp.PNG_FILTER_NONE,
|
||||
"Sub": Jimp.PNG_FILTER_SUB,
|
||||
"Up": Jimp.PNG_FILTER_UP,
|
||||
"Average": Jimp.PNG_FILTER_AVERAGE,
|
||||
"Paeth": Jimp.PNG_FILTER_PATH
|
||||
};
|
||||
|
||||
const mime = formatMap[format];
|
||||
@@ -98,7 +98,7 @@ class ConvertImageFormat extends Operation {
|
||||
}
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error opening image file. (${err})`);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Cover Image operation
|
||||
|
||||
@@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Crop Image operation
|
||||
@@ -99,7 +99,7 @@ class CropImage extends Operation {
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
@@ -119,9 +119,9 @@ class CropImage extends Operation {
|
||||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
||||
107
src/core/operations/DateTimeDelta.mjs
Normal file
107
src/core/operations/DateTimeDelta.mjs
Normal file
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* @author tomgond [tom.gonda@gmail.com]
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import moment from "moment-timezone";
|
||||
import {DATETIME_FORMATS, FORMAT_EXAMPLES} from "../lib/DateTime.mjs";
|
||||
|
||||
/**
|
||||
* DateTime Delta operation
|
||||
*/
|
||||
class DateTimeDelta extends Operation {
|
||||
|
||||
/**
|
||||
* DateTimeDelta constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "DateTime Delta";
|
||||
this.module = "Default";
|
||||
this.description = "Calculates a new DateTime value given an input DateTime value and a time difference (delta) from the input DateTime value.";
|
||||
this.inputType = "string";
|
||||
this.outputType = "html";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Built in formats",
|
||||
"type": "populateOption",
|
||||
"value": DATETIME_FORMATS,
|
||||
"target": 1
|
||||
},
|
||||
{
|
||||
"name": "Input format string",
|
||||
"type": "binaryString",
|
||||
"value": "DD/MM/YYYY HH:mm:ss"
|
||||
},
|
||||
{
|
||||
"name": "Time Operation",
|
||||
"type": "option",
|
||||
"value": ["Add", "Subtract"]
|
||||
},
|
||||
{
|
||||
"name": "Days",
|
||||
"type": "number",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"name": "Hours",
|
||||
"type": "number",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"name": "Minutes",
|
||||
"type": "number",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"name": "Seconds",
|
||||
"type": "number",
|
||||
"value": 0
|
||||
}
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const inputTimezone = "UTC";
|
||||
const inputFormat = args[1];
|
||||
const operationType = args[2];
|
||||
const daysDelta = args[3];
|
||||
const hoursDelta = args[4];
|
||||
const minutesDelta = args[5];
|
||||
const secondsDelta = args[6];
|
||||
let date = "";
|
||||
|
||||
try {
|
||||
date = moment.tz(input, inputFormat, inputTimezone);
|
||||
if (!date || date.format() === "Invalid date") throw Error;
|
||||
} catch (err) {
|
||||
return `Invalid format.\n\n${FORMAT_EXAMPLES}`;
|
||||
}
|
||||
let newDate;
|
||||
if (operationType === "Add") {
|
||||
newDate = date.add(daysDelta, "days")
|
||||
.add(hoursDelta, "hours")
|
||||
.add(minutesDelta, "minutes")
|
||||
.add(secondsDelta, "seconds");
|
||||
|
||||
} else {
|
||||
newDate = date.add(-daysDelta, "days")
|
||||
.add(-hoursDelta, "hours")
|
||||
.add(-minutesDelta, "minutes")
|
||||
.add(-secondsDelta, "seconds");
|
||||
}
|
||||
return newDate.tz(inputTimezone).format(inputFormat.replace(/[<>]/g, ""));
|
||||
}
|
||||
}
|
||||
|
||||
export default DateTimeDelta;
|
||||
@@ -62,11 +62,13 @@ class DeriveEVPKey extends Operation {
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const passphrase = Utils.convertToByteString(args[0].string, args[0].option),
|
||||
const passphrase = CryptoJS.enc.Latin1.parse(
|
||||
Utils.convertToByteString(args[0].string, args[0].option)),
|
||||
keySize = args[1] / 32,
|
||||
iterations = args[2],
|
||||
hasher = args[3],
|
||||
salt = Utils.convertToByteString(args[4].string, args[4].option),
|
||||
salt = CryptoJS.enc.Latin1.parse(
|
||||
Utils.convertToByteString(args[4].string, args[4].option)),
|
||||
key = CryptoJS.EvpKDF(passphrase, salt, { // lgtm [js/insufficient-password-hash]
|
||||
keySize: keySize,
|
||||
hasher: CryptoJS.algo[hasher],
|
||||
|
||||
@@ -119,9 +119,9 @@ class Diff extends Operation {
|
||||
|
||||
for (let i = 0; i < diff.length; i++) {
|
||||
if (diff[i].added) {
|
||||
if (showAdded) output += "<span class='hl5'>" + Utils.escapeHtml(diff[i].value) + "</span>";
|
||||
if (showAdded) output += "<ins>" + Utils.escapeHtml(diff[i].value) + "</ins>";
|
||||
} else if (diff[i].removed) {
|
||||
if (showRemoved) output += "<span class='hl3'>" + Utils.escapeHtml(diff[i].value) + "</span>";
|
||||
if (showRemoved) output += "<del>" + Utils.escapeHtml(diff[i].value) + "</del>";
|
||||
} else if (!showSubtraction) {
|
||||
output += Utils.escapeHtml(diff[i].value);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Image Dither operation
|
||||
@@ -44,7 +44,7 @@ class DitherImage extends Operation {
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
@@ -55,9 +55,9 @@ class DitherImage extends Operation {
|
||||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
||||
107
src/core/operations/ECDSASign.mjs
Normal file
107
src/core/operations/ECDSASign.mjs
Normal file
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* @author cplussharp
|
||||
* @copyright Crown Copyright 2021
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import { fromHex } from "../lib/Hex.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import r from "jsrsasign";
|
||||
|
||||
/**
|
||||
* ECDSA Sign operation
|
||||
*/
|
||||
class ECDSASign extends Operation {
|
||||
|
||||
/**
|
||||
* ECDSASign constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "ECDSA Sign";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Sign a plaintext message with a PEM encoded EC key.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "ECDSA Private Key (PEM)",
|
||||
type: "text",
|
||||
value: "-----BEGIN EC PRIVATE KEY-----"
|
||||
},
|
||||
{
|
||||
name: "Message Digest Algorithm",
|
||||
type: "option",
|
||||
value: [
|
||||
"SHA-256",
|
||||
"SHA-384",
|
||||
"SHA-512",
|
||||
"SHA-1",
|
||||
"MD5"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Output Format",
|
||||
type: "option",
|
||||
value: [
|
||||
"ASN.1 HEX",
|
||||
"P1363 HEX",
|
||||
"JSON Web Signature",
|
||||
"Raw JSON"
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [keyPem, mdAlgo, outputFormat] = args;
|
||||
|
||||
if (keyPem.replace("-----BEGIN EC PRIVATE KEY-----", "").length === 0) {
|
||||
throw new OperationError("Please enter a private key.");
|
||||
}
|
||||
|
||||
const internalAlgorithmName = mdAlgo.replace("-", "") + "withECDSA";
|
||||
const sig = new r.KJUR.crypto.Signature({ alg: internalAlgorithmName });
|
||||
const key = r.KEYUTIL.getKey(keyPem);
|
||||
if (key.type !== "EC") {
|
||||
throw new OperationError("Provided key is not an EC key.");
|
||||
}
|
||||
if (!key.isPrivate) {
|
||||
throw new OperationError("Provided key is not a private key.");
|
||||
}
|
||||
sig.init(key);
|
||||
const signatureASN1Hex = sig.signString(input);
|
||||
|
||||
let result;
|
||||
switch (outputFormat) {
|
||||
case "ASN.1 HEX":
|
||||
result = signatureASN1Hex;
|
||||
break;
|
||||
case "P1363 HEX":
|
||||
result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex);
|
||||
break;
|
||||
case "JSON Web Signature":
|
||||
result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex);
|
||||
result = toBase64(fromHex(result), "A-Za-z0-9-_"); // base64url
|
||||
break;
|
||||
case "Raw JSON": {
|
||||
const signatureRS = r.KJUR.crypto.ECDSA.parseSigHexInHexRS(signatureASN1Hex);
|
||||
result = JSON.stringify(signatureRS);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export default ECDSASign;
|
||||
146
src/core/operations/ECDSASignatureConversion.mjs
Normal file
146
src/core/operations/ECDSASignatureConversion.mjs
Normal file
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* @author cplussharp
|
||||
* @copyright Crown Copyright 2021
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import { fromBase64, toBase64 } from "../lib/Base64.mjs";
|
||||
import { fromHex, toHexFast } from "../lib/Hex.mjs";
|
||||
import r from "jsrsasign";
|
||||
|
||||
/**
|
||||
* ECDSA Sign operation
|
||||
*/
|
||||
class ECDSASignatureConversion extends Operation {
|
||||
|
||||
/**
|
||||
* ECDSASignatureConversion constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "ECDSA Signature Conversion";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Convert an ECDSA signature between hex, asn1 and json.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Input Format",
|
||||
type: "option",
|
||||
value: [
|
||||
"Auto",
|
||||
"ASN.1 HEX",
|
||||
"P1363 HEX",
|
||||
"JSON Web Signature",
|
||||
"Raw JSON"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Output Format",
|
||||
type: "option",
|
||||
value: [
|
||||
"ASN.1 HEX",
|
||||
"P1363 HEX",
|
||||
"JSON Web Signature",
|
||||
"Raw JSON"
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
let inputFormat = args[0];
|
||||
const outputFormat = args[1];
|
||||
|
||||
// detect input format
|
||||
let inputJson;
|
||||
if (inputFormat === "Auto") {
|
||||
try {
|
||||
inputJson = JSON.parse(input);
|
||||
if (typeof(inputJson) === "object") {
|
||||
inputFormat = "Raw JSON";
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if (inputFormat === "Auto") {
|
||||
const hexRegex = /^[a-f\d]{2,}$/gi;
|
||||
if (hexRegex.test(input)) {
|
||||
if (input.substring(0, 2) === "30" && r.ASN1HEX.isASN1HEX(input)) {
|
||||
inputFormat = "ASN.1 HEX";
|
||||
} else {
|
||||
inputFormat = "P1363 HEX";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let inputBase64;
|
||||
if (inputFormat === "Auto") {
|
||||
try {
|
||||
inputBase64 = fromBase64(input, "A-Za-z0-9-_", false);
|
||||
inputFormat = "JSON Web Signature";
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// convert input to ASN.1 hex
|
||||
let signatureASN1Hex;
|
||||
switch (inputFormat) {
|
||||
case "Auto":
|
||||
throw new OperationError("Signature format could not be detected");
|
||||
case "ASN.1 HEX":
|
||||
signatureASN1Hex = input;
|
||||
break;
|
||||
case "P1363 HEX":
|
||||
signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(input);
|
||||
break;
|
||||
case "JSON Web Signature":
|
||||
if (!inputBase64) inputBase64 = fromBase64(input, "A-Za-z0-9-_");
|
||||
signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(toHexFast(inputBase64));
|
||||
break;
|
||||
case "Raw JSON": {
|
||||
if (!inputJson) inputJson = JSON.parse(input);
|
||||
if (!inputJson.r) {
|
||||
throw new OperationError('No "r" value in the signature JSON');
|
||||
}
|
||||
if (!inputJson.s) {
|
||||
throw new OperationError('No "s" value in the signature JSON');
|
||||
}
|
||||
signatureASN1Hex = r.KJUR.crypto.ECDSA.hexRSSigToASN1Sig(inputJson.r, inputJson.s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// convert ASN.1 hex to output format
|
||||
let result;
|
||||
switch (outputFormat) {
|
||||
case "ASN.1 HEX":
|
||||
result = signatureASN1Hex;
|
||||
break;
|
||||
case "P1363 HEX":
|
||||
result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex);
|
||||
break;
|
||||
case "JSON Web Signature":
|
||||
result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex);
|
||||
result = toBase64(fromHex(result), "A-Za-z0-9-_"); // base64url
|
||||
break;
|
||||
case "Raw JSON": {
|
||||
const signatureRS = r.KJUR.crypto.ECDSA.parseSigHexInHexRS(signatureASN1Hex);
|
||||
result = JSON.stringify(signatureRS);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export default ECDSASignatureConversion;
|
||||
154
src/core/operations/ECDSAVerify.mjs
Normal file
154
src/core/operations/ECDSAVerify.mjs
Normal file
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* @author cplussharp
|
||||
* @copyright Crown Copyright 2021
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import { fromBase64 } from "../lib/Base64.mjs";
|
||||
import { toHexFast } from "../lib/Hex.mjs";
|
||||
import r from "jsrsasign";
|
||||
|
||||
/**
|
||||
* ECDSA Verify operation
|
||||
*/
|
||||
class ECDSAVerify extends Operation {
|
||||
|
||||
/**
|
||||
* ECDSAVerify constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "ECDSA Verify";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Verify a message against a signature and a public PEM encoded EC key.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Input Format",
|
||||
type: "option",
|
||||
value: [
|
||||
"Auto",
|
||||
"ASN.1 HEX",
|
||||
"P1363 HEX",
|
||||
"JSON Web Signature",
|
||||
"Raw JSON"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Message Digest Algorithm",
|
||||
type: "option",
|
||||
value: [
|
||||
"SHA-256",
|
||||
"SHA-384",
|
||||
"SHA-512",
|
||||
"SHA-1",
|
||||
"MD5"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "ECDSA Public Key (PEM)",
|
||||
type: "text",
|
||||
value: "-----BEGIN PUBLIC KEY-----"
|
||||
},
|
||||
{
|
||||
name: "Message",
|
||||
type: "text",
|
||||
value: ""
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
let inputFormat = args[0];
|
||||
const [, mdAlgo, keyPem, msg] = args;
|
||||
|
||||
if (keyPem.replace("-----BEGIN PUBLIC KEY-----", "").length === 0) {
|
||||
throw new OperationError("Please enter a public key.");
|
||||
}
|
||||
|
||||
// detect input format
|
||||
let inputJson;
|
||||
if (inputFormat === "Auto") {
|
||||
try {
|
||||
inputJson = JSON.parse(input);
|
||||
if (typeof(inputJson) === "object") {
|
||||
inputFormat = "Raw JSON";
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if (inputFormat === "Auto") {
|
||||
const hexRegex = /^[a-f\d]{2,}$/gi;
|
||||
if (hexRegex.test(input)) {
|
||||
if (input.substring(0, 2) === "30" && r.ASN1HEX.isASN1HEX(input)) {
|
||||
inputFormat = "ASN.1 HEX";
|
||||
} else {
|
||||
inputFormat = "P1363 HEX";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let inputBase64;
|
||||
if (inputFormat === "Auto") {
|
||||
try {
|
||||
inputBase64 = fromBase64(input, "A-Za-z0-9-_", false);
|
||||
inputFormat = "JSON Web Signature";
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// convert to ASN.1 signature
|
||||
let signatureASN1Hex;
|
||||
switch (inputFormat) {
|
||||
case "Auto":
|
||||
throw new OperationError("Signature format could not be detected");
|
||||
case "ASN.1 HEX":
|
||||
signatureASN1Hex = input;
|
||||
break;
|
||||
case "P1363 HEX":
|
||||
signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(input);
|
||||
break;
|
||||
case "JSON Web Signature":
|
||||
if (!inputBase64) inputBase64 = fromBase64(input, "A-Za-z0-9-_");
|
||||
signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(toHexFast(inputBase64));
|
||||
break;
|
||||
case "Raw JSON": {
|
||||
if (!inputJson) inputJson = JSON.parse(input);
|
||||
if (!inputJson.r) {
|
||||
throw new OperationError('No "r" value in the signature JSON');
|
||||
}
|
||||
if (!inputJson.s) {
|
||||
throw new OperationError('No "s" value in the signature JSON');
|
||||
}
|
||||
signatureASN1Hex = r.KJUR.crypto.ECDSA.hexRSSigToASN1Sig(inputJson.r, inputJson.s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// verify signature
|
||||
const internalAlgorithmName = mdAlgo.replace("-", "") + "withECDSA";
|
||||
const sig = new r.KJUR.crypto.Signature({ alg: internalAlgorithmName });
|
||||
const key = r.KEYUTIL.getKey(keyPem);
|
||||
if (key.type !== "EC") {
|
||||
throw new OperationError("Provided key is not an EC key.");
|
||||
}
|
||||
if (!key.isPublic) {
|
||||
throw new OperationError("Provided key is not a public key.");
|
||||
}
|
||||
sig.init(key);
|
||||
sig.updateString(msg);
|
||||
const result = sig.verify(signatureASN1Hex);
|
||||
return result ? "Verified OK" : "Verification Failure";
|
||||
}
|
||||
}
|
||||
|
||||
export default ECDSAVerify;
|
||||
84
src/core/operations/ExtractHashes.mjs
Normal file
84
src/core/operations/ExtractHashes.mjs
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* @author mshwed [m@ttshwed.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { search } from "../lib/Extract.mjs";
|
||||
|
||||
/**
|
||||
* Extract Hash Values operation
|
||||
*/
|
||||
class ExtractHashes extends Operation {
|
||||
|
||||
/**
|
||||
* ExtractHashValues constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Extract hashes";
|
||||
this.module = "Regex";
|
||||
this.description = "Extracts potential hashes based on hash character length";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Comparison_of_cryptographic_hash_functions";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Hash character length",
|
||||
type: "number",
|
||||
value: 40
|
||||
},
|
||||
{
|
||||
name: "All hashes",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "Display Total",
|
||||
type: "boolean",
|
||||
value: false
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const results = [];
|
||||
let hashCount = 0;
|
||||
|
||||
const [hashLength, searchAllHashes, showDisplayTotal] = args;
|
||||
|
||||
// Convert character length to bit length
|
||||
let hashBitLengths = [(hashLength / 2) * 8];
|
||||
|
||||
if (searchAllHashes) hashBitLengths = [4, 8, 16, 32, 64, 128, 160, 192, 224, 256, 320, 384, 512, 1024];
|
||||
|
||||
for (const hashBitLength of hashBitLengths) {
|
||||
// Convert bit length to character length
|
||||
const hashCharacterLength = (hashBitLength / 8) * 2;
|
||||
|
||||
const regex = new RegExp(`(\\b|^)[a-f0-9]{${hashCharacterLength}}(\\b|$)`, "g");
|
||||
const searchResults = search(input, regex, null, false);
|
||||
|
||||
hashCount += searchResults.length;
|
||||
results.push(...searchResults);
|
||||
}
|
||||
|
||||
let output = "";
|
||||
if (showDisplayTotal) {
|
||||
output = `Total Results: ${hashCount}\n\n`;
|
||||
}
|
||||
|
||||
output = output + results.join("\n");
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ExtractHashes;
|
||||
@@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import { fromBinary } from "../lib/Binary.mjs";
|
||||
import { isImage } from "../lib/FileType.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Extract LSB operation
|
||||
@@ -73,7 +73,7 @@ class ExtractLSB extends Operation {
|
||||
const bit = 7 - args.pop(),
|
||||
pixelOrder = args.pop(),
|
||||
colours = args.filter(option => option !== "").map(option => COLOUR_OPTIONS.indexOf(option)),
|
||||
parsedImage = await jimp.read(input),
|
||||
parsedImage = await Jimp.read(input),
|
||||
width = parsedImage.bitmap.width,
|
||||
height = parsedImage.bitmap.height,
|
||||
rgba = parsedImage.bitmap.data;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import { isImage } from "../lib/FileType.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
import {RGBA_DELIM_OPTIONS} from "../lib/Delim.mjs";
|
||||
|
||||
@@ -52,7 +52,7 @@ class ExtractRGBA extends Operation {
|
||||
|
||||
const delimiter = args[0],
|
||||
includeAlpha = args[1],
|
||||
parsedImage = await jimp.read(input);
|
||||
parsedImage = await Jimp.read(input);
|
||||
|
||||
let bitmap = parsedImage.bitmap.data;
|
||||
bitmap = includeAlpha ? bitmap : bitmap.filter((val, idx) => idx % 4 !== 3);
|
||||
|
||||
78
src/core/operations/FangURL.mjs
Normal file
78
src/core/operations/FangURL.mjs
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* @author arnydo [github@arnydo.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
|
||||
/**
|
||||
* FangURL operation
|
||||
*/
|
||||
class FangURL extends Operation {
|
||||
|
||||
/**
|
||||
* FangURL constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Fang URL";
|
||||
this.module = "Default";
|
||||
this.description = "Takes a 'Defanged' Universal Resource Locator (URL) and 'Fangs' it. Meaning, it removes the alterations (defanged) that render it useless so that it can be used again.";
|
||||
this.infoURL = "https://isc.sans.edu/forums/diary/Defang+all+the+things/22744/";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Restore [.]",
|
||||
type: "boolean",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: "Restore hxxp",
|
||||
type: "boolean",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: "Restore ://",
|
||||
type: "boolean",
|
||||
value: true
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [dots, http, slashes] = args;
|
||||
|
||||
input = fangURL(input, dots, http, slashes);
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Defangs a given URL
|
||||
*
|
||||
* @param {string} url
|
||||
* @param {boolean} dots
|
||||
* @param {boolean} http
|
||||
* @param {boolean} slashes
|
||||
* @returns {string}
|
||||
*/
|
||||
function fangURL(url, dots, http, slashes) {
|
||||
if (dots) url = url.replace(/\[\.\]/g, ".");
|
||||
if (http) url = url.replace(/hxxp/g, "http");
|
||||
if (slashes) url = url.replace(/\[:\/\/\]/g, "://");
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
export default FangURL;
|
||||
63
src/core/operations/FernetDecrypt.mjs
Normal file
63
src/core/operations/FernetDecrypt.mjs
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* @author Karsten Silkenbäumer [github.com/kassi]
|
||||
* @copyright Karsten Silkenbäumer 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import fernet from "fernet";
|
||||
|
||||
/**
|
||||
* FernetDecrypt operation
|
||||
*/
|
||||
class FernetDecrypt extends Operation {
|
||||
/**
|
||||
* FernetDecrypt constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Fernet Decrypt";
|
||||
this.module = "Default";
|
||||
this.description = "Fernet is a symmetric encryption method which makes sure that the message encrypted cannot be manipulated/read without the key. It uses URL safe encoding for the keys. Fernet uses 128-bit AES in CBC mode and PKCS7 padding, with HMAC using SHA256 for authentication. The IV is created from os.random().<br><br><b>Key:</b> The key must be 32 bytes (256 bits) encoded with Base64.";
|
||||
this.infoURL = "https://asecuritysite.com/encryption/fer";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Key",
|
||||
"type": "string",
|
||||
"value": ""
|
||||
},
|
||||
];
|
||||
this.patterns = [
|
||||
{
|
||||
match: "^[A-Z\\d\\-_=]{20,}$",
|
||||
flags: "i",
|
||||
args: []
|
||||
},
|
||||
];
|
||||
}
|
||||
/**
|
||||
* @param {String} input
|
||||
* @param {Object[]} args
|
||||
* @returns {String}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [secretInput] = args;
|
||||
try {
|
||||
const secret = new fernet.Secret(secretInput);
|
||||
const token = new fernet.Token({
|
||||
secret: secret,
|
||||
token: input,
|
||||
ttl: 0
|
||||
});
|
||||
return token.decode();
|
||||
} catch (err) {
|
||||
throw new OperationError(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default FernetDecrypt;
|
||||
54
src/core/operations/FernetEncrypt.mjs
Normal file
54
src/core/operations/FernetEncrypt.mjs
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* @author Karsten Silkenbäumer [github.com/kassi]
|
||||
* @copyright Karsten Silkenbäumer 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import fernet from "fernet";
|
||||
|
||||
/**
|
||||
* FernetEncrypt operation
|
||||
*/
|
||||
class FernetEncrypt extends Operation {
|
||||
/**
|
||||
* FernetEncrypt constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Fernet Encrypt";
|
||||
this.module = "Default";
|
||||
this.description = "Fernet is a symmetric encryption method which makes sure that the message encrypted cannot be manipulated/read without the key. It uses URL safe encoding for the keys. Fernet uses 128-bit AES in CBC mode and PKCS7 padding, with HMAC using SHA256 for authentication. The IV is created from os.random().<br><br><b>Key:</b> The key must be 32 bytes (256 bits) encoded with Base64.";
|
||||
this.infoURL = "https://asecuritysite.com/encryption/fer";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Key",
|
||||
"type": "string",
|
||||
"value": ""
|
||||
},
|
||||
];
|
||||
}
|
||||
/**
|
||||
* @param {String} input
|
||||
* @param {Object[]} args
|
||||
* @returns {String}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [secretInput] = args;
|
||||
try {
|
||||
const secret = new fernet.Secret(secretInput);
|
||||
const token = new fernet.Token({
|
||||
secret: secret,
|
||||
});
|
||||
return token.encode(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default FernetEncrypt;
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @author sw5678
|
||||
* @copyright Crown Copyright 2016
|
||||
* @copyright Crown Copyright 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
@@ -21,7 +21,8 @@ class FileTree extends Operation {
|
||||
|
||||
this.name = "File Tree";
|
||||
this.module = "Default";
|
||||
this.description = "Creates file tree from list of file paths (similar to the tree command in Linux)";
|
||||
this.description = "Creates a file tree from a list of file paths (similar to the tree command in Linux)";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Tree_(command)";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
|
||||
@@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Flip Image operation
|
||||
@@ -51,7 +51,7 @@ class FlipImage extends Operation {
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
@@ -69,9 +69,9 @@ class FlipImage extends Operation {
|
||||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
||||
@@ -60,7 +60,7 @@ class FromBase58 extends Operation {
|
||||
run(input, args) {
|
||||
let alphabet = args[0] || ALPHABET_OPTIONS[0].value;
|
||||
const removeNonAlphaChars = args[1] === undefined ? true : args[1],
|
||||
result = [0];
|
||||
result = [];
|
||||
|
||||
alphabet = Utils.expandAlphRange(alphabet).join("");
|
||||
|
||||
@@ -87,11 +87,9 @@ class FromBase58 extends Operation {
|
||||
}
|
||||
}
|
||||
|
||||
let carry = result[0] * 58 + index;
|
||||
result[0] = carry & 0xFF;
|
||||
carry = carry >> 8;
|
||||
let carry = index;
|
||||
|
||||
for (let i = 1; i < result.length; i++) {
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
carry += result[i] * 58;
|
||||
result[i] = carry & 0xFF;
|
||||
carry = carry >> 8;
|
||||
|
||||
78
src/core/operations/FromFloat.mjs
Normal file
78
src/core/operations/FromFloat.mjs
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* @author tcode2k16 [tcode2k16@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import ieee754 from "ieee754";
|
||||
import {DELIM_OPTIONS} from "../lib/Delim.mjs";
|
||||
|
||||
/**
|
||||
* From Float operation
|
||||
*/
|
||||
class FromFloat extends Operation {
|
||||
|
||||
/**
|
||||
* FromFloat constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "From Float";
|
||||
this.module = "Default";
|
||||
this.description = "Convert from IEEE754 Floating Point Numbers";
|
||||
this.infoURL = "https://wikipedia.org/wiki/IEEE_754";
|
||||
this.inputType = "string";
|
||||
this.outputType = "byteArray";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Endianness",
|
||||
"type": "option",
|
||||
"value": [
|
||||
"Big Endian",
|
||||
"Little Endian"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Size",
|
||||
"type": "option",
|
||||
"value": [
|
||||
"Float (4 bytes)",
|
||||
"Double (8 bytes)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Delimiter",
|
||||
"type": "option",
|
||||
"value": DELIM_OPTIONS
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
run(input, args) {
|
||||
if (input.length === 0) return [];
|
||||
|
||||
const [endianness, size, delimiterName] = args;
|
||||
const delim = Utils.charRep(delimiterName || "Space");
|
||||
const byteSize = size === "Double (8 bytes)" ? 8 : 4;
|
||||
const isLE = endianness === "Little Endian";
|
||||
const mLen = byteSize === 4 ? 23 : 52;
|
||||
const floats = input.split(delim);
|
||||
|
||||
const output = new Array(floats.length*byteSize);
|
||||
for (let i = 0; i < floats.length; i++) {
|
||||
ieee754.write(output, parseFloat(floats[i]), i*byteSize, isLE, mLen, byteSize);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default FromFloat;
|
||||
@@ -55,22 +55,19 @@ class GOSTDecrypt extends Operation {
|
||||
type: "argSelector",
|
||||
value: [
|
||||
{
|
||||
name: "GOST 28147 (Magma, 1989)",
|
||||
off: [5],
|
||||
on: [6]
|
||||
name: "GOST 28147 (1989)",
|
||||
on: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Magma, 2015)",
|
||||
off: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Kuznyechik, 2015)",
|
||||
on: [5],
|
||||
off: [6]
|
||||
off: [5]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Block length",
|
||||
type: "option",
|
||||
value: ["64", "128"]
|
||||
},
|
||||
{
|
||||
name: "sBox",
|
||||
type: "option",
|
||||
@@ -100,14 +97,30 @@ class GOSTDecrypt extends Operation {
|
||||
* @returns {string}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [keyObj, ivObj, inputType, outputType, version, length, sBox, blockMode, keyMeshing, padding] = args;
|
||||
const [keyObj, ivObj, inputType, outputType, version, sBox, blockMode, keyMeshing, padding] = args;
|
||||
|
||||
const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option));
|
||||
const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option));
|
||||
input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input));
|
||||
|
||||
const versionNum = version === "GOST 28147 (Magma, 1989)" ? 1989 : 2015;
|
||||
const blockLength = versionNum === 1989 ? 64 : parseInt(length, 10);
|
||||
let blockLength, versionNum;
|
||||
switch (version) {
|
||||
case "GOST 28147 (1989)":
|
||||
versionNum = 1989;
|
||||
blockLength = 64;
|
||||
break;
|
||||
case "GOST R 34.12 (Magma, 2015)":
|
||||
versionNum = 2015;
|
||||
blockLength = 64;
|
||||
break;
|
||||
case "GOST R 34.12 (Kuznyechik, 2015)":
|
||||
versionNum = 2015;
|
||||
blockLength = 128;
|
||||
break;
|
||||
default:
|
||||
throw new OperationError(`Unknown algorithm version: ${version}`);
|
||||
}
|
||||
|
||||
const sBoxVal = versionNum === 1989 ? sBox : null;
|
||||
|
||||
const algorithm = {
|
||||
|
||||
@@ -55,22 +55,19 @@ class GOSTEncrypt extends Operation {
|
||||
type: "argSelector",
|
||||
value: [
|
||||
{
|
||||
name: "GOST 28147 (Magma, 1989)",
|
||||
off: [5],
|
||||
on: [6]
|
||||
name: "GOST 28147 (1989)",
|
||||
on: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Magma, 2015)",
|
||||
off: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Kuznyechik, 2015)",
|
||||
on: [5],
|
||||
off: [6]
|
||||
off: [5]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Block length",
|
||||
type: "option",
|
||||
value: ["64", "128"]
|
||||
},
|
||||
{
|
||||
name: "sBox",
|
||||
type: "option",
|
||||
@@ -100,14 +97,30 @@ class GOSTEncrypt extends Operation {
|
||||
* @returns {string}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [keyObj, ivObj, inputType, outputType, version, length, sBox, blockMode, keyMeshing, padding] = args;
|
||||
const [keyObj, ivObj, inputType, outputType, version, sBox, blockMode, keyMeshing, padding] = args;
|
||||
|
||||
const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option));
|
||||
const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option));
|
||||
input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input));
|
||||
|
||||
const versionNum = version === "GOST 28147 (Magma, 1989)" ? 1989 : 2015;
|
||||
const blockLength = versionNum === 1989 ? 64 : parseInt(length, 10);
|
||||
let blockLength, versionNum;
|
||||
switch (version) {
|
||||
case "GOST 28147 (1989)":
|
||||
versionNum = 1989;
|
||||
blockLength = 64;
|
||||
break;
|
||||
case "GOST R 34.12 (Magma, 2015)":
|
||||
versionNum = 2015;
|
||||
blockLength = 64;
|
||||
break;
|
||||
case "GOST R 34.12 (Kuznyechik, 2015)":
|
||||
versionNum = 2015;
|
||||
blockLength = 128;
|
||||
break;
|
||||
default:
|
||||
throw new OperationError(`Unknown algorithm version: ${version}`);
|
||||
}
|
||||
|
||||
const sBoxVal = versionNum === 1989 ? sBox : null;
|
||||
|
||||
const algorithm = {
|
||||
|
||||
@@ -55,22 +55,19 @@ class GOSTKeyUnwrap extends Operation {
|
||||
type: "argSelector",
|
||||
value: [
|
||||
{
|
||||
name: "GOST 28147 (Magma, 1989)",
|
||||
off: [5],
|
||||
on: [6]
|
||||
name: "GOST 28147 (1989)",
|
||||
on: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Magma, 2015)",
|
||||
off: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Kuznyechik, 2015)",
|
||||
on: [5],
|
||||
off: [6]
|
||||
off: [5]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Block length",
|
||||
type: "option",
|
||||
value: ["64", "128"]
|
||||
},
|
||||
{
|
||||
name: "sBox",
|
||||
type: "option",
|
||||
@@ -90,14 +87,30 @@ class GOSTKeyUnwrap extends Operation {
|
||||
* @returns {string}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [keyObj, ukmObj, inputType, outputType, version, length, sBox, keyWrapping] = args;
|
||||
const [keyObj, ukmObj, inputType, outputType, version, sBox, keyWrapping] = args;
|
||||
|
||||
const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option));
|
||||
const ukm = toHexFast(Utils.convertToByteArray(ukmObj.string, ukmObj.option));
|
||||
input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input));
|
||||
|
||||
const versionNum = version === "GOST 28147 (Magma, 1989)" ? 1989 : 2015;
|
||||
const blockLength = versionNum === 1989 ? 64 : parseInt(length, 10);
|
||||
let blockLength, versionNum;
|
||||
switch (version) {
|
||||
case "GOST 28147 (1989)":
|
||||
versionNum = 1989;
|
||||
blockLength = 64;
|
||||
break;
|
||||
case "GOST R 34.12 (Magma, 2015)":
|
||||
versionNum = 2015;
|
||||
blockLength = 64;
|
||||
break;
|
||||
case "GOST R 34.12 (Kuznyechik, 2015)":
|
||||
versionNum = 2015;
|
||||
blockLength = 128;
|
||||
break;
|
||||
default:
|
||||
throw new OperationError(`Unknown algorithm version: ${version}`);
|
||||
}
|
||||
|
||||
const sBoxVal = versionNum === 1989 ? sBox : null;
|
||||
|
||||
const algorithm = {
|
||||
|
||||
@@ -55,22 +55,19 @@ class GOSTKeyWrap extends Operation {
|
||||
type: "argSelector",
|
||||
value: [
|
||||
{
|
||||
name: "GOST 28147 (Magma, 1989)",
|
||||
off: [5],
|
||||
on: [6]
|
||||
name: "GOST 28147 (1989)",
|
||||
on: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Magma, 2015)",
|
||||
off: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Kuznyechik, 2015)",
|
||||
on: [5],
|
||||
off: [6]
|
||||
off: [5]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Block length",
|
||||
type: "option",
|
||||
value: ["64", "128"]
|
||||
},
|
||||
{
|
||||
name: "sBox",
|
||||
type: "option",
|
||||
@@ -90,14 +87,30 @@ class GOSTKeyWrap extends Operation {
|
||||
* @returns {string}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [keyObj, ukmObj, inputType, outputType, version, length, sBox, keyWrapping] = args;
|
||||
const [keyObj, ukmObj, inputType, outputType, version, sBox, keyWrapping] = args;
|
||||
|
||||
const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option));
|
||||
const ukm = toHexFast(Utils.convertToByteArray(ukmObj.string, ukmObj.option));
|
||||
input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input));
|
||||
|
||||
const versionNum = version === "GOST 28147 (Magma, 1989)" ? 1989 : 2015;
|
||||
const blockLength = versionNum === 1989 ? 64 : parseInt(length, 10);
|
||||
let blockLength, versionNum;
|
||||
switch (version) {
|
||||
case "GOST 28147 (1989)":
|
||||
versionNum = 1989;
|
||||
blockLength = 64;
|
||||
break;
|
||||
case "GOST R 34.12 (Magma, 2015)":
|
||||
versionNum = 2015;
|
||||
blockLength = 64;
|
||||
break;
|
||||
case "GOST R 34.12 (Kuznyechik, 2015)":
|
||||
versionNum = 2015;
|
||||
blockLength = 128;
|
||||
break;
|
||||
default:
|
||||
throw new OperationError(`Unknown algorithm version: ${version}`);
|
||||
}
|
||||
|
||||
const sBoxVal = versionNum === 1989 ? sBox : null;
|
||||
|
||||
const algorithm = {
|
||||
|
||||
@@ -55,22 +55,19 @@ class GOSTSign extends Operation {
|
||||
type: "argSelector",
|
||||
value: [
|
||||
{
|
||||
name: "GOST 28147 (Magma, 1989)",
|
||||
off: [5],
|
||||
on: [6]
|
||||
name: "GOST 28147 (1989)",
|
||||
on: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Magma, 2015)",
|
||||
off: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Kuznyechik, 2015)",
|
||||
on: [5],
|
||||
off: [6]
|
||||
off: [5]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Block length",
|
||||
type: "option",
|
||||
value: ["64", "128"]
|
||||
},
|
||||
{
|
||||
name: "sBox",
|
||||
type: "option",
|
||||
@@ -93,14 +90,30 @@ class GOSTSign extends Operation {
|
||||
* @returns {string}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [keyObj, ivObj, inputType, outputType, version, length, sBox, macLength] = args;
|
||||
const [keyObj, ivObj, inputType, outputType, version, sBox, macLength] = args;
|
||||
|
||||
const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option));
|
||||
const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option));
|
||||
input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input));
|
||||
|
||||
const versionNum = version === "GOST 28147 (Magma, 1989)" ? 1989 : 2015;
|
||||
const blockLength = versionNum === 1989 ? 64 : parseInt(length, 10);
|
||||
let blockLength, versionNum;
|
||||
switch (version) {
|
||||
case "GOST 28147 (1989)":
|
||||
versionNum = 1989;
|
||||
blockLength = 64;
|
||||
break;
|
||||
case "GOST R 34.12 (Magma, 2015)":
|
||||
versionNum = 2015;
|
||||
blockLength = 64;
|
||||
break;
|
||||
case "GOST R 34.12 (Kuznyechik, 2015)":
|
||||
versionNum = 2015;
|
||||
blockLength = 128;
|
||||
break;
|
||||
default:
|
||||
throw new OperationError(`Unknown algorithm version: ${version}`);
|
||||
}
|
||||
|
||||
const sBoxVal = versionNum === 1989 ? sBox : null;
|
||||
|
||||
const algorithm = {
|
||||
|
||||
@@ -56,22 +56,19 @@ class GOSTVerify extends Operation {
|
||||
type: "argSelector",
|
||||
value: [
|
||||
{
|
||||
name: "GOST 28147 (Magma, 1989)",
|
||||
off: [5],
|
||||
on: [6]
|
||||
name: "GOST 28147 (1989)",
|
||||
on: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Magma, 2015)",
|
||||
off: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Kuznyechik, 2015)",
|
||||
on: [5],
|
||||
off: [6]
|
||||
off: [5]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Block length",
|
||||
type: "option",
|
||||
value: ["64", "128"]
|
||||
},
|
||||
{
|
||||
name: "sBox",
|
||||
type: "option",
|
||||
@@ -86,15 +83,31 @@ class GOSTVerify extends Operation {
|
||||
* @returns {string}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [keyObj, ivObj, macObj, inputType, version, length, sBox] = args;
|
||||
const [keyObj, ivObj, macObj, inputType, version, sBox] = args;
|
||||
|
||||
const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option));
|
||||
const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option));
|
||||
const mac = toHexFast(Utils.convertToByteArray(macObj.string, macObj.option));
|
||||
input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input));
|
||||
|
||||
const versionNum = version === "GOST 28147 (Magma, 1989)" ? 1989 : 2015;
|
||||
const blockLength = versionNum === 1989 ? 64 : parseInt(length, 10);
|
||||
let blockLength, versionNum;
|
||||
switch (version) {
|
||||
case "GOST 28147 (1989)":
|
||||
versionNum = 1989;
|
||||
blockLength = 64;
|
||||
break;
|
||||
case "GOST R 34.12 (Magma, 2015)":
|
||||
versionNum = 2015;
|
||||
blockLength = 64;
|
||||
break;
|
||||
case "GOST R 34.12 (Kuznyechik, 2015)":
|
||||
versionNum = 2015;
|
||||
blockLength = 128;
|
||||
break;
|
||||
default:
|
||||
throw new OperationError(`Unknown algorithm version: ${version}`);
|
||||
}
|
||||
|
||||
const sBoxVal = versionNum === 1989 ? sBox : null;
|
||||
|
||||
const algorithm = {
|
||||
|
||||
102
src/core/operations/GenerateECDSAKeyPair.mjs
Normal file
102
src/core/operations/GenerateECDSAKeyPair.mjs
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* @author cplussharp
|
||||
* @copyright Crown Copyright 2021
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { cryptNotice } from "../lib/Crypt.mjs";
|
||||
import r from "jsrsasign";
|
||||
|
||||
/**
|
||||
* Generate ECDSA Key Pair operation
|
||||
*/
|
||||
class GenerateECDSAKeyPair extends Operation {
|
||||
|
||||
/**
|
||||
* GenerateECDSAKeyPair constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Generate ECDSA Key Pair";
|
||||
this.module = "Ciphers";
|
||||
this.description = `Generate an ECDSA key pair with a given Curve.<br><br>${cryptNotice}`;
|
||||
this.infoURL = "https://wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Elliptic Curve",
|
||||
type: "option",
|
||||
value: [
|
||||
"P-256",
|
||||
"P-384",
|
||||
"P-521"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Output Format",
|
||||
type: "option",
|
||||
value: [
|
||||
"PEM",
|
||||
"DER",
|
||||
"JWK"
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [curveName, outputFormat] = args;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let internalCurveName;
|
||||
switch (curveName) {
|
||||
case "P-256":
|
||||
internalCurveName = "secp256r1";
|
||||
break;
|
||||
case "P-384":
|
||||
internalCurveName = "secp384r1";
|
||||
break;
|
||||
case "P-521":
|
||||
internalCurveName = "secp521r1";
|
||||
break;
|
||||
}
|
||||
const keyPair = r.KEYUTIL.generateKeypair("EC", internalCurveName);
|
||||
|
||||
let pubKey;
|
||||
let privKey;
|
||||
let result;
|
||||
switch (outputFormat) {
|
||||
case "PEM":
|
||||
pubKey = r.KEYUTIL.getPEM(keyPair.pubKeyObj).replace(/\r/g, "");
|
||||
privKey = r.KEYUTIL.getPEM(keyPair.prvKeyObj, "PKCS8PRV").replace(/\r/g, "");
|
||||
result = pubKey + "\n" + privKey;
|
||||
break;
|
||||
case "DER":
|
||||
result = keyPair.prvKeyObj.prvKeyHex;
|
||||
break;
|
||||
case "JWK":
|
||||
pubKey = r.KEYUTIL.getJWKFromKey(keyPair.pubKeyObj);
|
||||
pubKey.key_ops = ["verify"]; // eslint-disable-line camelcase
|
||||
pubKey.kid = "PublicKey";
|
||||
privKey = r.KEYUTIL.getJWKFromKey(keyPair.prvKeyObj);
|
||||
privKey.key_ops = ["sign"]; // eslint-disable-line camelcase
|
||||
privKey.kid = "PrivateKey";
|
||||
result = JSON.stringify({keys: [privKey, pubKey]}, null, 4);
|
||||
break;
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default GenerateECDSAKeyPair;
|
||||
@@ -10,7 +10,7 @@ import Utils from "../Utils.mjs";
|
||||
import {isImage} from "../lib/FileType.mjs";
|
||||
import {toBase64} from "../lib/Base64.mjs";
|
||||
import {isWorkerEnvironment} from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Generate Image operation
|
||||
@@ -81,7 +81,7 @@ class GenerateImage extends Operation {
|
||||
}
|
||||
|
||||
const height = Math.ceil(input.length / bytesPerPixel / width);
|
||||
const image = await new jimp(width, height, (err, image) => {});
|
||||
const image = await new Jimp(width, height, (err, image) => {});
|
||||
|
||||
if (isWorkerEnvironment())
|
||||
self.sendStatusMessage("Generating image from data...");
|
||||
@@ -95,7 +95,7 @@ class GenerateImage extends Operation {
|
||||
const y = Math.floor(index / width);
|
||||
|
||||
const value = curByte[k] === "0" ? 0xFF : 0x00;
|
||||
const pixel = jimp.rgbaToInt(value, value, value, 0xFF);
|
||||
const pixel = Jimp.rgbaToInt(value, value, value, 0xFF);
|
||||
image.setPixelColor(pixel, x, y);
|
||||
}
|
||||
}
|
||||
@@ -139,7 +139,7 @@ class GenerateImage extends Operation {
|
||||
}
|
||||
|
||||
try {
|
||||
const pixel = jimp.rgbaToInt(red, green, blue, alpha);
|
||||
const pixel = Jimp.rgbaToInt(red, green, blue, alpha);
|
||||
image.setPixelColor(pixel, x, y);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error while generating image from pixel values. (${err})`);
|
||||
@@ -151,11 +151,11 @@ class GenerateImage extends Operation {
|
||||
if (isWorkerEnvironment())
|
||||
self.sendStatusMessage("Scaling image...");
|
||||
|
||||
image.scaleToFit(width*scale, height*scale, jimp.RESIZE_NEAREST_NEIGHBOR);
|
||||
image.scaleToFit(width*scale, height*scale, Jimp.RESIZE_NEAREST_NEIGHBOR);
|
||||
}
|
||||
|
||||
try {
|
||||
const imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
const imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error generating image. (${err})`);
|
||||
|
||||
@@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Image Brightness / Contrast operation
|
||||
@@ -60,7 +60,7 @@ class ImageBrightnessContrast extends Operation {
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
@@ -78,9 +78,9 @@ class ImageBrightnessContrast extends Operation {
|
||||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Image Filter operation
|
||||
@@ -54,7 +54,7 @@ class ImageFilter extends Operation {
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
@@ -69,9 +69,9 @@ class ImageFilter extends Operation {
|
||||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Image Hue/Saturation/Lightness operation
|
||||
@@ -68,7 +68,7 @@ class ImageHueSaturationLightness extends Operation {
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
@@ -106,9 +106,9 @@ class ImageHueSaturationLightness extends Operation {
|
||||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Image Opacity operation
|
||||
@@ -53,7 +53,7 @@ class ImageOpacity extends Operation {
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
@@ -64,9 +64,9 @@ class ImageOpacity extends Operation {
|
||||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Invert Image operation
|
||||
@@ -44,7 +44,7 @@ class InvertImage extends Operation {
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
@@ -55,9 +55,9 @@ class InvertImage extends Operation {
|
||||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
||||
73
src/core/operations/JA4Fingerprint.mjs
Normal file
73
src/core/operations/JA4Fingerprint.mjs
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import {toJA4} from "../lib/JA4.mjs";
|
||||
|
||||
/**
|
||||
* JA4 Fingerprint operation
|
||||
*/
|
||||
class JA4Fingerprint extends Operation {
|
||||
|
||||
/**
|
||||
* JA4Fingerprint constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "JA4 Fingerprint";
|
||||
this.module = "Crypto";
|
||||
this.description = "Generates a JA4 fingerprint to help identify TLS clients based on hashing together values from the Client Hello.<br><br>Input: A hex stream of the TLS or QUIC Client Hello packet application layer.";
|
||||
this.infoURL = "https://medium.com/foxio/ja4-network-fingerprinting-9376fe9ca637";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Input format",
|
||||
type: "option",
|
||||
value: ["Hex", "Base64", "Raw"]
|
||||
},
|
||||
{
|
||||
name: "Output format",
|
||||
type: "option",
|
||||
value: ["JA4", "JA4 Original Rendering", "JA4 Raw", "JA4 Raw Original Rendering", "All"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [inputFormat, outputFormat] = args;
|
||||
input = Utils.convertToByteArray(input, inputFormat);
|
||||
const ja4 = toJA4(new Uint8Array(input));
|
||||
|
||||
// Output
|
||||
switch (outputFormat) {
|
||||
case "JA4":
|
||||
return ja4.JA4;
|
||||
case "JA4 Original Rendering":
|
||||
return ja4.JA4_o;
|
||||
case "JA4 Raw":
|
||||
return ja4.JA4_r;
|
||||
case "JA4 Raw Original Rendering":
|
||||
return ja4.JA4_ro;
|
||||
case "All":
|
||||
default:
|
||||
return `JA4: ${ja4.JA4}
|
||||
JA4_o: ${ja4.JA4_o}
|
||||
JA4_r: ${ja4.JA4_r}
|
||||
JA4_ro: ${ja4.JA4_ro}`;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default JA4Fingerprint;
|
||||
66
src/core/operations/JA4ServerFingerprint.mjs
Normal file
66
src/core/operations/JA4ServerFingerprint.mjs
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import {toJA4S} from "../lib/JA4.mjs";
|
||||
|
||||
/**
|
||||
* JA4Server Fingerprint operation
|
||||
*/
|
||||
class JA4ServerFingerprint extends Operation {
|
||||
|
||||
/**
|
||||
* JA4ServerFingerprint constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "JA4Server Fingerprint";
|
||||
this.module = "Crypto";
|
||||
this.description = "Generates a JA4Server Fingerprint (JA4S) to help identify TLS servers or sessions based on hashing together values from the Server Hello.<br><br>Input: A hex stream of the TLS or QUIC Server Hello packet application layer.";
|
||||
this.infoURL = "https://medium.com/foxio/ja4-network-fingerprinting-9376fe9ca637";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Input format",
|
||||
type: "option",
|
||||
value: ["Hex", "Base64", "Raw"]
|
||||
},
|
||||
{
|
||||
name: "Output format",
|
||||
type: "option",
|
||||
value: ["JA4S", "JA4S Raw", "Both"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [inputFormat, outputFormat] = args;
|
||||
input = Utils.convertToByteArray(input, inputFormat);
|
||||
const ja4s = toJA4S(new Uint8Array(input));
|
||||
|
||||
// Output
|
||||
switch (outputFormat) {
|
||||
case "JA4S":
|
||||
return ja4s.JA4S;
|
||||
case "JA4S Raw":
|
||||
return ja4s.JA4S_r;
|
||||
case "Both":
|
||||
default:
|
||||
return `JA4S: ${ja4s.JA4S}\nJA4S_r: ${ja4s.JA4S_r}`;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default JA4ServerFingerprint;
|
||||
@@ -35,12 +35,6 @@ class JPathExpression extends Operation {
|
||||
name: "Result delimiter",
|
||||
type: "binaryShortString",
|
||||
value: "\\n"
|
||||
},
|
||||
{
|
||||
name: "Prevent eval",
|
||||
type: "boolean",
|
||||
value: true,
|
||||
description: "Evaluated expressions are disabled by default for security reasons"
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -51,7 +45,7 @@ class JPathExpression extends Operation {
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [query, delimiter, preventEval] = args;
|
||||
const [query, delimiter] = args;
|
||||
let results, jsonObj;
|
||||
|
||||
try {
|
||||
@@ -63,8 +57,7 @@ class JPathExpression extends Operation {
|
||||
try {
|
||||
results = JSONPath({
|
||||
path: query,
|
||||
json: jsonObj,
|
||||
preventEval: preventEval
|
||||
json: jsonObj
|
||||
});
|
||||
} catch (err) {
|
||||
throw new OperationError(`Invalid JPath expression: ${err.message}`);
|
||||
|
||||
80
src/core/operations/JWKToPem.mjs
Normal file
80
src/core/operations/JWKToPem.mjs
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* @author cplussharp
|
||||
* @copyright Crown Copyright 2021
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import r from "jsrsasign";
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* PEM to JWK operation
|
||||
*/
|
||||
class PEMToJWK extends Operation {
|
||||
|
||||
/**
|
||||
* PEMToJWK constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "JWK to PEM";
|
||||
this.module = "PublicKey";
|
||||
this.description = "Converts Keys in JSON Web Key format to PEM format (PKCS#8).";
|
||||
this.infoURL = "https://datatracker.ietf.org/doc/html/rfc7517";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
this.checks = [
|
||||
{
|
||||
"pattern": "\"kty\":\\s*\"(EC|RSA)\"",
|
||||
"flags": "gm",
|
||||
"args": []
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const inputJson = JSON.parse(input);
|
||||
|
||||
let keys = [];
|
||||
if (Array.isArray(inputJson)) {
|
||||
// list of keys => transform all keys
|
||||
keys = inputJson;
|
||||
} else if (Array.isArray(inputJson.keys)) {
|
||||
// JSON Web Key Set => transform all keys
|
||||
keys = inputJson.keys;
|
||||
} else if (typeof inputJson === "object") {
|
||||
// single key
|
||||
keys.push(inputJson);
|
||||
} else {
|
||||
throw new OperationError("Input is not a JSON Web Key");
|
||||
}
|
||||
|
||||
let output = "";
|
||||
for (let i=0; i<keys.length; i++) {
|
||||
const jwk = keys[i];
|
||||
if (typeof jwk.kty !== "string") {
|
||||
throw new OperationError("Invalid JWK format");
|
||||
} else if ("|RSA|EC|".indexOf(jwk.kty) === -1) {
|
||||
throw new OperationError(`Unsupported JWK key type '${inputJson.kty}'`);
|
||||
}
|
||||
|
||||
const key = r.KEYUTIL.getKey(jwk);
|
||||
const pem = key.isPrivate ? r.KEYUTIL.getPEM(key, "PKCS8PRV") : r.KEYUTIL.getPEM(key);
|
||||
|
||||
// PEM ends with '\n', so a new key always starts on a new line
|
||||
output += pem;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
export default PEMToJWK;
|
||||
@@ -36,6 +36,11 @@ class JWTSign extends Operation {
|
||||
name: "Signing algorithm",
|
||||
type: "option",
|
||||
value: JWT_ALGORITHMS
|
||||
},
|
||||
{
|
||||
name: "Header",
|
||||
type: "text",
|
||||
value: "{}"
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -46,11 +51,12 @@ class JWTSign extends Operation {
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [key, algorithm] = args;
|
||||
const [key, algorithm, header] = args;
|
||||
|
||||
try {
|
||||
return jwt.sign(input, key, {
|
||||
algorithm: algorithm === "None" ? "none" : algorithm
|
||||
algorithm: algorithm === "None" ? "none" : algorithm,
|
||||
header: JSON.parse(header || "{}")
|
||||
});
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error: Have you entered the key correctly? The key should be either the secret for HMAC algorithms or the PEM-encoded private key for RSA and ECDSA.
|
||||
|
||||
@@ -22,7 +22,7 @@ class JWTVerify extends Operation {
|
||||
|
||||
this.name = "JWT Verify";
|
||||
this.module = "Crypto";
|
||||
this.description = "Verifies that a JSON Web Token is valid and has been signed with the provided secret / private key.<br><br>The key should be either the secret for HMAC algorithms or the PEM-encoded private key for RSA and ECDSA.";
|
||||
this.description = "Verifies that a JSON Web Token is valid and has been signed with the provided secret / private key.<br><br>The key should be either the secret for HMAC algorithms or the PEM-encoded public key for RSA and ECDSA.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/JSON_Web_Token";
|
||||
this.inputType = "string";
|
||||
this.outputType = "JSON";
|
||||
|
||||
@@ -22,7 +22,7 @@ class MurmurHash3 extends Operation {
|
||||
super();
|
||||
|
||||
this.name = "MurmurHash3";
|
||||
this.module = "Default";
|
||||
this.module = "Hashing";
|
||||
this.description = "Generates a MurmurHash v3 for a string input and an optional seed input";
|
||||
this.infoURL = "https://wikipedia.org/wiki/MurmurHash";
|
||||
this.inputType = "string";
|
||||
@@ -115,11 +115,7 @@ class MurmurHash3 extends Operation {
|
||||
* @return {number} 32-bit signed integer
|
||||
*/
|
||||
unsignedToSigned(value) {
|
||||
if (value & 0x80000000) {
|
||||
return -0x100000000 + value;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
return value & 0x80000000 ? -0x100000000 + value : value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,7 +8,7 @@ import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Normalise Image operation
|
||||
@@ -43,7 +43,7 @@ class NormaliseImage extends Operation {
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error opening image file. (${err})`);
|
||||
}
|
||||
@@ -53,9 +53,9 @@ class NormaliseImage extends Operation {
|
||||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
||||
@@ -12,9 +12,10 @@ import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
|
||||
import process from "process";
|
||||
import { createWorker } from "tesseract.js";
|
||||
|
||||
const OEM_MODES = ["Tesseract only", "LSTM only", "Tesseract/LSTM Combined"];
|
||||
|
||||
/**
|
||||
* Optical Character Recognition operation
|
||||
*/
|
||||
@@ -37,6 +38,12 @@ class OpticalCharacterRecognition extends Operation {
|
||||
name: "Show confidence",
|
||||
type: "boolean",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: "OCR Engine Mode",
|
||||
type: "option",
|
||||
value: OEM_MODES,
|
||||
defaultIndex: 1
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -47,7 +54,7 @@ class OpticalCharacterRecognition extends Operation {
|
||||
* @returns {string}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [showConfidence] = args;
|
||||
const [showConfidence, oemChoice] = args;
|
||||
|
||||
if (!isWorkerEnvironment()) throw new OperationError("This operation only works in a browser");
|
||||
|
||||
@@ -56,12 +63,13 @@ class OpticalCharacterRecognition extends Operation {
|
||||
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/`;
|
||||
const assetDir = `${self.docURL}/assets/`;
|
||||
const oem = OEM_MODES.indexOf(oemChoice);
|
||||
|
||||
try {
|
||||
self.sendStatusMessage("Spinning up Tesseract worker...");
|
||||
const image = `data:${type};base64,${toBase64(input)}`;
|
||||
const worker = createWorker({
|
||||
const worker = await createWorker("eng", oem, {
|
||||
workerPath: `${assetDir}tesseract/worker.min.js`,
|
||||
langPath: `${assetDir}tesseract/lang-data`,
|
||||
corePath: `${assetDir}tesseract/tesseract-core.wasm.js`,
|
||||
@@ -71,11 +79,6 @@ class OpticalCharacterRecognition extends Operation {
|
||||
}
|
||||
}
|
||||
});
|
||||
await worker.load();
|
||||
self.sendStatusMessage(`Loading English language pack...`);
|
||||
await worker.loadLanguage("eng");
|
||||
self.sendStatusMessage("Intialising Tesseract API...");
|
||||
await worker.initialize("eng");
|
||||
self.sendStatusMessage("Finding text...");
|
||||
const result = await worker.recognize(image);
|
||||
|
||||
|
||||
88
src/core/operations/PEMToJWK.mjs
Normal file
88
src/core/operations/PEMToJWK.mjs
Normal file
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* @author cplussharp
|
||||
* @copyright Crown Copyright 2021
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import r from "jsrsasign";
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* PEM to JWK operation
|
||||
*/
|
||||
class PEMToJWK extends Operation {
|
||||
|
||||
/**
|
||||
* PEMToJWK constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "PEM to JWK";
|
||||
this.module = "PublicKey";
|
||||
this.description = "Converts Keys in PEM format to a JSON Web Key format.";
|
||||
this.infoURL = "https://datatracker.ietf.org/doc/html/rfc7517";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
this.checks = [
|
||||
{
|
||||
"pattern": "-----BEGIN ((RSA |EC )?(PRIVATE|PUBLIC) KEY|CERTIFICATE)-----",
|
||||
"args": []
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
let 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 header = input.substring(match.index, indexBase64);
|
||||
const footer = `-----END ${match[1]}-----`;
|
||||
const indexFooter = input.indexOf(footer, indexBase64);
|
||||
if (indexFooter === -1) {
|
||||
throw new OperationError(`PEM footer '${footer}' not found`);
|
||||
}
|
||||
|
||||
const pem = input.substring(match.index, indexFooter + footer.length);
|
||||
if (match[1].indexOf("KEY") !== -1) {
|
||||
if (header === "-----BEGIN RSA PUBLIC KEY-----") {
|
||||
throw new OperationError("Unsupported RSA public key format. Only PKCS#8 is supported.");
|
||||
}
|
||||
|
||||
const key = r.KEYUTIL.getKey(pem);
|
||||
if (key.type === "DSA") {
|
||||
throw new OperationError("DSA keys are not supported for JWK");
|
||||
}
|
||||
const jwk = r.KEYUTIL.getJWKFromKey(key);
|
||||
if (output.length > 0) {
|
||||
output += "\n";
|
||||
}
|
||||
output += JSON.stringify(jwk);
|
||||
} else if (match[1] === "CERTIFICATE") {
|
||||
const cert = new r.X509();
|
||||
cert.readCertPEM(pem);
|
||||
const key = cert.getPublicKey();
|
||||
const jwk = r.KEYUTIL.getJWKFromKey(key);
|
||||
if (output.length > 0) {
|
||||
output += "\n";
|
||||
}
|
||||
output += JSON.stringify(jwk);
|
||||
} else {
|
||||
throw new OperationError(`Unsupported PEM type '${match[1]}'`);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
export default PEMToJWK;
|
||||
390
src/core/operations/ParseCSR.mjs
Normal file
390
src/core/operations/ParseCSR.mjs
Normal file
@@ -0,0 +1,390 @@
|
||||
/**
|
||||
* @author jkataja
|
||||
* @copyright Crown Copyright 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import r from "jsrsasign";
|
||||
import Operation from "../Operation.mjs";
|
||||
import { formatDnObj } from "../lib/PublicKey.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* Parse CSR operation
|
||||
*/
|
||||
class ParseCSR extends Operation {
|
||||
|
||||
/**
|
||||
* ParseCSR constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Parse CSR";
|
||||
this.module = "PublicKey";
|
||||
this.description = "Parse Certificate Signing Request (CSR) for an X.509 certificate";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Certificate_signing_request";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Input format",
|
||||
"type": "option",
|
||||
"value": ["PEM"]
|
||||
}
|
||||
];
|
||||
this.checks = [
|
||||
{
|
||||
"pattern": "^-+BEGIN CERTIFICATE REQUEST-+\\r?\\n[\\da-z+/\\n\\r]+-+END CERTIFICATE REQUEST-+\\r?\\n?$",
|
||||
"flags": "i",
|
||||
"args": ["PEM"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string} Human-readable description of a Certificate Signing Request (CSR).
|
||||
*/
|
||||
run(input, args) {
|
||||
if (!input.length) {
|
||||
return "No input";
|
||||
}
|
||||
|
||||
// Parse the CSR into JSON parameters
|
||||
const csrParam = new r.KJUR.asn1.csr.CSRUtil.getParam(input);
|
||||
|
||||
return `Subject\n${formatDnObj(csrParam.subject, 2)}
|
||||
Public Key${formatSubjectPublicKey(csrParam.sbjpubkey)}
|
||||
Signature${formatSignature(csrParam.sigalg, csrParam.sighex)}
|
||||
Requested Extensions${formatRequestedExtensions(csrParam)}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format signature of a CSR
|
||||
* @param {*} sigAlg string
|
||||
* @param {*} sigHex string
|
||||
* @returns Multi-line string describing CSR Signature
|
||||
*/
|
||||
function formatSignature(sigAlg, sigHex) {
|
||||
let out = `\n`;
|
||||
|
||||
out += ` Algorithm: ${sigAlg}\n`;
|
||||
|
||||
if (new RegExp("withdsa", "i").test(sigAlg)) {
|
||||
const d = new r.KJUR.crypto.DSA();
|
||||
const sigParam = d.parseASN1Signature(sigHex);
|
||||
out += ` Signature:
|
||||
R: ${formatHexOntoMultiLine(absBigIntToHex(sigParam[0]))}
|
||||
S: ${formatHexOntoMultiLine(absBigIntToHex(sigParam[1]))}\n`;
|
||||
} else if (new RegExp("withrsa", "i").test(sigAlg)) {
|
||||
out += ` Signature: ${formatHexOntoMultiLine(sigHex)}\n`;
|
||||
} else {
|
||||
out += ` Signature: ${formatHexOntoMultiLine(ensureHexIsPositiveInTwosComplement(sigHex))}\n`;
|
||||
}
|
||||
|
||||
return chop(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format Subject Public Key from PEM encoded public key string
|
||||
* @param {*} publicKeyPEM string
|
||||
* @returns Multi-line string describing Subject Public Key Info
|
||||
*/
|
||||
function formatSubjectPublicKey(publicKeyPEM) {
|
||||
let out = "\n";
|
||||
|
||||
const publicKey = r.KEYUTIL.getKey(publicKeyPEM);
|
||||
if (publicKey instanceof r.RSAKey) {
|
||||
out += ` Algorithm: RSA
|
||||
Length: ${publicKey.n.bitLength()} bits
|
||||
Modulus: ${formatHexOntoMultiLine(absBigIntToHex(publicKey.n))}
|
||||
Exponent: ${publicKey.e} (0x${Utils.hex(publicKey.e)})\n`;
|
||||
} else if (publicKey instanceof r.KJUR.crypto.ECDSA) {
|
||||
out += ` Algorithm: ECDSA
|
||||
Length: ${publicKey.ecparams.keylen} bits
|
||||
Pub: ${formatHexOntoMultiLine(publicKey.pubKeyHex)}
|
||||
ASN1 OID: ${r.KJUR.crypto.ECDSA.getName(publicKey.getShortNISTPCurveName())}
|
||||
NIST CURVE: ${publicKey.getShortNISTPCurveName()}\n`;
|
||||
} else if (publicKey instanceof r.KJUR.crypto.DSA) {
|
||||
out += ` Algorithm: DSA
|
||||
Length: ${publicKey.p.toString(16).length * 4} bits
|
||||
Pub: ${formatHexOntoMultiLine(absBigIntToHex(publicKey.y))}
|
||||
P: ${formatHexOntoMultiLine(absBigIntToHex(publicKey.p))}
|
||||
Q: ${formatHexOntoMultiLine(absBigIntToHex(publicKey.q))}
|
||||
G: ${formatHexOntoMultiLine(absBigIntToHex(publicKey.g))}\n`;
|
||||
} else {
|
||||
out += `unsupported public key algorithm\n`;
|
||||
}
|
||||
|
||||
return chop(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format known extensions of a CSR
|
||||
* @param {*} csrParam object
|
||||
* @returns Multi-line string describing CSR Requested Extensions
|
||||
*/
|
||||
function formatRequestedExtensions(csrParam) {
|
||||
const formattedExtensions = new Array(4).fill("");
|
||||
|
||||
if (Object.hasOwn(csrParam, "extreq")) {
|
||||
for (const extension of csrParam.extreq) {
|
||||
let parts = [];
|
||||
switch (extension.extname) {
|
||||
case "basicConstraints" :
|
||||
parts = describeBasicConstraints(extension);
|
||||
formattedExtensions[0] = ` Basic Constraints:${formatExtensionCriticalTag(extension)}\n${indent(4, parts)}`;
|
||||
break;
|
||||
case "keyUsage" :
|
||||
parts = describeKeyUsage(extension);
|
||||
formattedExtensions[1] = ` Key Usage:${formatExtensionCriticalTag(extension)}\n${indent(4, parts)}`;
|
||||
break;
|
||||
case "extKeyUsage" :
|
||||
parts = describeExtendedKeyUsage(extension);
|
||||
formattedExtensions[2] = ` Extended Key Usage:${formatExtensionCriticalTag(extension)}\n${indent(4, parts)}`;
|
||||
break;
|
||||
case "subjectAltName" :
|
||||
parts = describeSubjectAlternativeName(extension);
|
||||
formattedExtensions[3] = ` Subject Alternative Name:${formatExtensionCriticalTag(extension)}\n${indent(4, parts)}`;
|
||||
break;
|
||||
default :
|
||||
parts = ["(unsuported extension)"];
|
||||
formattedExtensions.push(` ${extension.extname}:${formatExtensionCriticalTag(extension)}\n${indent(4, parts)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let out = "\n";
|
||||
|
||||
formattedExtensions.forEach((formattedExtension) => {
|
||||
if (formattedExtension !== undefined && formattedExtension !== null && formattedExtension.length !== 0) {
|
||||
out += formattedExtension;
|
||||
}
|
||||
});
|
||||
|
||||
return chop(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format extension critical tag
|
||||
* @param {*} extension Object
|
||||
* @returns String describing whether the extension is critical or not
|
||||
*/
|
||||
function formatExtensionCriticalTag(extension) {
|
||||
return Object.hasOwn(extension, "critical") && extension.critical ? " critical" : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Format string input as a comma separated hex string on multiple lines
|
||||
* @param {*} hex String
|
||||
* @returns Multi-line string describing the Hex input
|
||||
*/
|
||||
function formatHexOntoMultiLine(hex) {
|
||||
if (hex.length % 2 !== 0) {
|
||||
hex = "0" + hex;
|
||||
}
|
||||
|
||||
return formatMultiLine(chop(hex.replace(/(..)/g, "$&:")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert BigInt to abs value in Hex
|
||||
* @param {*} int BigInt
|
||||
* @returns String representing absolute value in Hex
|
||||
*/
|
||||
function absBigIntToHex(int) {
|
||||
int = int < 0n ? -int : int;
|
||||
|
||||
return ensureHexIsPositiveInTwosComplement(int.toString(16));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure Hex String remains positive in 2's complement
|
||||
* @param {*} hex String
|
||||
* @returns Hex String ensuring value remains positive in 2's complement
|
||||
*/
|
||||
function ensureHexIsPositiveInTwosComplement(hex) {
|
||||
if (hex.length % 2 !== 0) {
|
||||
return "0" + hex;
|
||||
}
|
||||
|
||||
// prepend 00 if most significant bit is 1 (sign bit)
|
||||
if (hex.length >=2 && (parseInt(hex.substring(0, 2), 16) & 128)) {
|
||||
hex = "00" + hex;
|
||||
}
|
||||
|
||||
return hex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format string onto multiple lines
|
||||
* @param {*} longStr
|
||||
* @returns String as a multi-line string
|
||||
*/
|
||||
function formatMultiLine(longStr) {
|
||||
const lines = [];
|
||||
|
||||
for (let remain = longStr ; remain !== "" ; remain = remain.substring(48)) {
|
||||
lines.push(remain.substring(0, 48));
|
||||
}
|
||||
|
||||
return lines.join("\n ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe Basic Constraints
|
||||
* @see RFC 5280 4.2.1.9. Basic Constraints https://www.ietf.org/rfc/rfc5280.txt
|
||||
* @param {*} extension CSR extension with the name `basicConstraints`
|
||||
* @returns Array of strings describing Basic Constraints
|
||||
*/
|
||||
function describeBasicConstraints(extension) {
|
||||
const constraints = [];
|
||||
|
||||
constraints.push(`CA = ${Object.hasOwn(extension, "cA") && extension.cA ? "true" : "false"}`);
|
||||
if (Object.hasOwn(extension, "pathLen")) constraints.push(`PathLenConstraint = ${extension.pathLen}`);
|
||||
|
||||
return constraints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe Key Usage extension permitted use cases
|
||||
* @see RFC 5280 4.2.1.3. Key Usage https://www.ietf.org/rfc/rfc5280.txt
|
||||
* @param {*} extension CSR extension with the name `keyUsage`
|
||||
* @returns Array of strings describing Key Usage extension permitted use cases
|
||||
*/
|
||||
function describeKeyUsage(extension) {
|
||||
const usage = [];
|
||||
|
||||
const kuIdentifierToName = {
|
||||
digitalSignature: "Digital Signature",
|
||||
nonRepudiation: "Non-repudiation",
|
||||
keyEncipherment: "Key encipherment",
|
||||
dataEncipherment: "Data encipherment",
|
||||
keyAgreement: "Key agreement",
|
||||
keyCertSign: "Key certificate signing",
|
||||
cRLSign: "CRL signing",
|
||||
encipherOnly: "Encipher Only",
|
||||
decipherOnly: "Decipher Only",
|
||||
};
|
||||
|
||||
if (Object.hasOwn(extension, "names")) {
|
||||
extension.names.forEach((ku) => {
|
||||
if (Object.hasOwn(kuIdentifierToName, ku)) {
|
||||
usage.push(kuIdentifierToName[ku]);
|
||||
} else {
|
||||
usage.push(`unknown key usage (${ku})`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (usage.length === 0) usage.push("(none)");
|
||||
|
||||
return usage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe Extended Key Usage extension permitted use cases
|
||||
* @see RFC 5280 4.2.1.12. Extended Key Usage https://www.ietf.org/rfc/rfc5280.txt
|
||||
* @param {*} extension CSR extension with the name `extendedKeyUsage`
|
||||
* @returns Array of strings describing Extended Key Usage extension permitted use cases
|
||||
*/
|
||||
function describeExtendedKeyUsage(extension) {
|
||||
const usage = [];
|
||||
|
||||
const ekuIdentifierToName = {
|
||||
"serverAuth": "TLS Web Server Authentication",
|
||||
"clientAuth": "TLS Web Client Authentication",
|
||||
"codeSigning": "Code signing",
|
||||
"emailProtection": "E-mail Protection (S/MIME)",
|
||||
"timeStamping": "Trusted Timestamping",
|
||||
"1.3.6.1.4.1.311.2.1.21": "Microsoft Individual Code Signing", // msCodeInd
|
||||
"1.3.6.1.4.1.311.2.1.22": "Microsoft Commercial Code Signing", // msCodeCom
|
||||
"1.3.6.1.4.1.311.10.3.1": "Microsoft Trust List Signing", // msCTLSign
|
||||
"1.3.6.1.4.1.311.10.3.3": "Microsoft Server Gated Crypto", // msSGC
|
||||
"1.3.6.1.4.1.311.10.3.4": "Microsoft Encrypted File System", // msEFS
|
||||
"1.3.6.1.4.1.311.20.2.2": "Microsoft Smartcard Login", // msSmartcardLogin
|
||||
"2.16.840.1.113730.4.1": "Netscape Server Gated Crypto", // nsSGC
|
||||
};
|
||||
|
||||
if (Object.hasOwn(extension, "array")) {
|
||||
extension.array.forEach((eku) => {
|
||||
if (Object.hasOwn(ekuIdentifierToName, eku)) {
|
||||
usage.push(ekuIdentifierToName[eku]);
|
||||
} else {
|
||||
usage.push(eku);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (usage.length === 0) usage.push("(none)");
|
||||
|
||||
return usage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format Subject Alternative Names from the name `subjectAltName` extension
|
||||
* @see RFC 5280 4.2.1.6. Subject Alternative Name https://www.ietf.org/rfc/rfc5280.txt
|
||||
* @param {*} extension object
|
||||
* @returns Array of strings describing Subject Alternative Name extension
|
||||
*/
|
||||
function describeSubjectAlternativeName(extension) {
|
||||
const names = [];
|
||||
|
||||
if (Object.hasOwn(extension, "extname") && extension.extname === "subjectAltName") {
|
||||
if (Object.hasOwn(extension, "array")) {
|
||||
for (const altName of extension.array) {
|
||||
Object.keys(altName).forEach((key) => {
|
||||
switch (key) {
|
||||
case "rfc822":
|
||||
names.push(`EMAIL: ${altName[key]}`);
|
||||
break;
|
||||
case "dns":
|
||||
names.push(`DNS: ${altName[key]}`);
|
||||
break;
|
||||
case "uri":
|
||||
names.push(`URI: ${altName[key]}`);
|
||||
break;
|
||||
case "ip":
|
||||
names.push(`IP: ${altName[key]}`);
|
||||
break;
|
||||
case "dn":
|
||||
names.push(`DIR: ${altName[key].str}`);
|
||||
break;
|
||||
case "other" :
|
||||
names.push(`Other: ${altName[key].oid}::${altName[key].value.utf8str.str}`);
|
||||
break;
|
||||
default:
|
||||
names.push(`(unable to format SAN '${key}':${altName[key]})\n`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Join an array of strings and add leading spaces to each line.
|
||||
* @param {*} n How many leading spaces
|
||||
* @param {*} parts Array of strings
|
||||
* @returns Joined and indented string.
|
||||
*/
|
||||
function indent(n, parts) {
|
||||
const fluff = " ".repeat(n);
|
||||
return fluff + parts.join("\n" + fluff) + "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove last character from a string.
|
||||
* @param {*} s String
|
||||
* @returns Chopped string.
|
||||
*/
|
||||
function chop(s) {
|
||||
return s.substring(0, s.length - 1);
|
||||
}
|
||||
|
||||
export default ParseCSR;
|
||||
884
src/core/operations/ParseTLSRecord.mjs
Normal file
884
src/core/operations/ParseTLSRecord.mjs
Normal file
@@ -0,0 +1,884 @@
|
||||
/**
|
||||
* @author c65722 []
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import {toHexFast} from "../lib/Hex.mjs";
|
||||
import {objToTable} from "../lib/Protocol.mjs";
|
||||
import Stream from "../lib/Stream.mjs";
|
||||
|
||||
/**
|
||||
* Parse TLS record operation.
|
||||
*/
|
||||
class ParseTLSRecord extends Operation {
|
||||
|
||||
/**
|
||||
* ParseTLSRecord constructor.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Parse TLS record";
|
||||
this.module = "Default";
|
||||
this.description = "Parses one or more TLS records";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Transport_Layer_Security";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "json";
|
||||
this.presentType = "html";
|
||||
this.args = [];
|
||||
this._handshakeParser = new HandshakeParser();
|
||||
this._contentTypes = new Map();
|
||||
|
||||
for (const key in ContentType) {
|
||||
this._contentTypes[ContentType[key]] = key.toString().toLocaleLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input - Stream, containing one or more raw TLS Records.
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} Array of Object representations of TLS Records contained within input.
|
||||
*/
|
||||
run(input, args) {
|
||||
const s = new Stream(new Uint8Array(input));
|
||||
|
||||
const output = [];
|
||||
|
||||
while (s.hasMore()) {
|
||||
const record = this._readRecord(s);
|
||||
if (record) {
|
||||
output.push(record);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a TLS Record from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw TLS Record.
|
||||
* @returns {Object} Object representation of TLS Record.
|
||||
*/
|
||||
_readRecord(input) {
|
||||
const RECORD_HEADER_LEN = 5;
|
||||
|
||||
if (input.position + RECORD_HEADER_LEN > input.length) {
|
||||
input.moveTo(input.length);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const type = input.readInt(1);
|
||||
const typeString = this._contentTypes[type] ?? type.toString();
|
||||
const version = "0x" + toHexFast(input.getBytes(2));
|
||||
const length = input.readInt(2);
|
||||
const content = input.getBytes(length);
|
||||
const truncated = content.length < length;
|
||||
|
||||
const recordHeader = new RecordHeader(typeString, version, length, truncated);
|
||||
|
||||
if (!content.length) {
|
||||
return {...recordHeader};
|
||||
}
|
||||
|
||||
if (type === ContentType.HANDSHAKE) {
|
||||
return this._handshakeParser.parse(new Stream(content), recordHeader);
|
||||
}
|
||||
|
||||
const record = {...recordHeader};
|
||||
record.value = "0x" + toHexFast(content);
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the parsed TLS Records in a tabular style.
|
||||
*
|
||||
* @param {Object[]} data - Array of Object representations of the TLS Records.
|
||||
* @returns {html} HTML representation of TLS Records contained within data.
|
||||
*/
|
||||
present(data) {
|
||||
return data.map(r => objToTable(r)).join("\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
export default ParseTLSRecord;
|
||||
|
||||
/**
|
||||
* Repesents the known values of type field of a TLS Record header.
|
||||
*/
|
||||
const ContentType = Object.freeze({
|
||||
CHANGE_CIPHER_SPEC: 20,
|
||||
ALERT: 21,
|
||||
HANDSHAKE: 22,
|
||||
APPLICATION_DATA: 23,
|
||||
});
|
||||
|
||||
/**
|
||||
* Represents a TLS Record header
|
||||
*/
|
||||
class RecordHeader {
|
||||
/**
|
||||
* RecordHeader cosntructor.
|
||||
*
|
||||
* @param {string} type - String representation of TLS Record type field.
|
||||
* @param {string} version - Hex representation of TLS Record version field.
|
||||
* @param {int} length - Length of TLS Record.
|
||||
* @param {bool} truncated - Is TLS Record truncated.
|
||||
*/
|
||||
constructor(type, version, length, truncated) {
|
||||
this.type = type;
|
||||
this.version = version;
|
||||
this.length = length;
|
||||
|
||||
if (truncated) {
|
||||
this.truncated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses TLS Handshake messages.
|
||||
*/
|
||||
class HandshakeParser {
|
||||
|
||||
/**
|
||||
* HandshakeParser constructor.
|
||||
*/
|
||||
constructor() {
|
||||
this._clientHelloParser = new ClientHelloParser();
|
||||
this._serverHelloParser = new ServerHelloParser();
|
||||
this._newSessionTicketParser = new NewSessionTicketParser();
|
||||
this._certificateParser = new CertificateParser();
|
||||
this._certificateRequestParser = new CertificateRequestParser();
|
||||
this._certificateVerifyParser = new CertificateVerifyParser();
|
||||
this._handshakeTypes = new Map();
|
||||
|
||||
for (const key in HandshakeType) {
|
||||
this._handshakeTypes[HandshakeType[key]] = key.toString().toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a single TLS handshake message.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw Handshake message.
|
||||
* @param {RecordHeader} recordHeader - TLS Record header.
|
||||
* @returns {Object} Object representation of Handshake.
|
||||
*/
|
||||
parse(input, recordHeader) {
|
||||
const output = {...recordHeader};
|
||||
|
||||
if (!input.hasMore()) {
|
||||
return output;
|
||||
}
|
||||
|
||||
const handshakeType = input.readInt(1);
|
||||
output.handshakeType = this._handshakeTypes[handshakeType] ?? handshakeType.toString();
|
||||
|
||||
if (input.position + 3 > input.length) {
|
||||
input.moveTo(input.length);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
const handshakeLength = input.readInt(3);
|
||||
|
||||
if (handshakeLength + 4 !== recordHeader.length) {
|
||||
input.moveTo(0);
|
||||
|
||||
output.handshakeType = this._handshakeTypes[HandshakeType.FINISHED];
|
||||
output.handshakeValue = "0x" + toHexFast(input.bytes);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
const content = input.getBytes(handshakeLength);
|
||||
if (!content.length) {
|
||||
return output;
|
||||
}
|
||||
|
||||
switch (handshakeType) {
|
||||
case HandshakeType.CLIENT_HELLO:
|
||||
return {...output, ...this._clientHelloParser.parse(new Stream(content))};
|
||||
case HandshakeType.SERVER_HELLO:
|
||||
return {...output, ...this._serverHelloParser.parse(new Stream(content))};
|
||||
case HandshakeType.NEW_SESSION_TICKET:
|
||||
return {...output, ...this._newSessionTicketParser.parse(new Stream(content))};
|
||||
case HandshakeType.CERTIFICATE:
|
||||
return {...output, ...this._certificateParser.parse(new Stream(content))};
|
||||
case HandshakeType.CERTIFICATE_REQUEST:
|
||||
return {...output, ...this._certificateRequestParser.parse(new Stream(content))};
|
||||
case HandshakeType.CERTIFICATE_VERIFY:
|
||||
return {...output, ...this._certificateVerifyParser.parse(new Stream(content))};
|
||||
default:
|
||||
output.handshakeValue = "0x" + toHexFast(content);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the known values of the msg_type field of a TLS Handshake message.
|
||||
*/
|
||||
const HandshakeType = Object.freeze({
|
||||
HELLO_REQUEST: 0,
|
||||
CLIENT_HELLO: 1,
|
||||
SERVER_HELLO: 2,
|
||||
NEW_SESSION_TICKET: 4,
|
||||
CERTIFICATE: 11,
|
||||
SERVER_KEY_EXCHANGE: 12,
|
||||
CERTIFICATE_REQUEST: 13,
|
||||
SERVER_HELLO_DONE: 14,
|
||||
CERTIFICATE_VERIFY: 15,
|
||||
CLIENT_KEY_EXCHANGE: 16,
|
||||
FINISHED: 20,
|
||||
});
|
||||
|
||||
/**
|
||||
* Parses TLS Handshake ClientHello messages.
|
||||
*/
|
||||
class ClientHelloParser {
|
||||
|
||||
/**
|
||||
* ClientHelloParser constructor.
|
||||
*/
|
||||
constructor() {
|
||||
this._extensionsParser = new ExtensionsParser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a single TLS Handshake ClientHello message.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ClientHello message.
|
||||
* @returns {Object} Object representation of ClientHello.
|
||||
*/
|
||||
parse(input) {
|
||||
const output = {};
|
||||
|
||||
output.clientVersion = this._readClientVersion(input);
|
||||
output.random = this._readRandom(input);
|
||||
|
||||
const sessionID = this._readSessionID(input);
|
||||
if (sessionID) {
|
||||
output.sessionID = sessionID;
|
||||
}
|
||||
|
||||
output.cipherSuites = this._readCipherSuites(input);
|
||||
output.compressionMethods = this._readCompressionMethods(input);
|
||||
output.extensions = this._readExtensions(input);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the client_version field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ClientHello message, with position before client_version field.
|
||||
* @returns {string} Hex representation of client_version.
|
||||
*/
|
||||
_readClientVersion(input) {
|
||||
return readBytesAsHex(input, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the random field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ClientHello message, with position before random field.
|
||||
* @returns {string} Hex representation of random.
|
||||
*/
|
||||
_readRandom(input) {
|
||||
return readBytesAsHex(input, 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the session_id field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ClientHello message, with position before session_id length field.
|
||||
* @returns {string} Hex representation of session_id, or empty string if session_id not present.
|
||||
*/
|
||||
_readSessionID(input) {
|
||||
return readSizePrefixedBytesAsHex(input, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the cipher_suites field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ClientHello message, with position before cipher_suites length field.
|
||||
* @returns {Object} Object represention of cipher_suites field.
|
||||
*/
|
||||
_readCipherSuites(input) {
|
||||
const output = {};
|
||||
|
||||
output.length = input.readInt(2);
|
||||
if (!output.length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const cipherSuites = new Stream(input.getBytes(output.length));
|
||||
if (cipherSuites.length < output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
output.values = [];
|
||||
|
||||
while (cipherSuites.hasMore()) {
|
||||
const cipherSuite = readBytesAsHex(cipherSuites, 2);
|
||||
if (cipherSuite) {
|
||||
output.values.push(cipherSuite);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the compression_methods field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ClientHello message, with position before compression_methods length field.
|
||||
* @returns {Object} Object representation of compression_methods field.
|
||||
*/
|
||||
_readCompressionMethods(input) {
|
||||
const output = {};
|
||||
|
||||
output.length = input.readInt(1);
|
||||
if (!output.length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const compressionMethods = new Stream(input.getBytes(output.length));
|
||||
if (compressionMethods.length < output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
output.values = [];
|
||||
|
||||
while (compressionMethods.hasMore()) {
|
||||
const compressionMethod = readBytesAsHex(compressionMethods, 1);
|
||||
if (compressionMethod) {
|
||||
output.values.push(compressionMethod);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the extensions field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ClientHello message, with position before extensions length field.
|
||||
* @returns {Object} Object representations of extensions field.
|
||||
*/
|
||||
_readExtensions(input) {
|
||||
const output = {};
|
||||
|
||||
output.length = input.readInt(2);
|
||||
if (!output.length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const extensions = new Stream(input.getBytes(output.length));
|
||||
if (extensions.length < output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
output.values = this._extensionsParser.parse(extensions);
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses TLS Handshake ServeHello messages.
|
||||
*/
|
||||
class ServerHelloParser {
|
||||
|
||||
/**
|
||||
* ServerHelloParser constructor.
|
||||
*/
|
||||
constructor() {
|
||||
this._extensionsParser = new ExtensionsParser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a single TLS Handshake ServerHello message.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ServerHello message.
|
||||
* @return {Object} Object representation of ServerHello.
|
||||
*/
|
||||
parse(input) {
|
||||
const output = {};
|
||||
|
||||
output.serverVersion = this._readServerVersion(input);
|
||||
output.random = this._readRandom(input);
|
||||
|
||||
const sessionID = this._readSessionID(input);
|
||||
if (sessionID) {
|
||||
output.sessionID = sessionID;
|
||||
}
|
||||
|
||||
output.cipherSuite = this._readCipherSuite(input);
|
||||
output.compressionMethod = this._readCompressionMethod(input);
|
||||
output.extensions = this._readExtensions(input);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the server_version field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ServerHello message, with position before server_version field.
|
||||
* @returns {string} Hex representation of server_version.
|
||||
*/
|
||||
_readServerVersion(input) {
|
||||
return readBytesAsHex(input, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the random field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ServerHello message, with position before random field.
|
||||
* @returns {string} Hex representation of random.
|
||||
*/
|
||||
_readRandom(input) {
|
||||
return readBytesAsHex(input, 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the session_id field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ServertHello message, with position before session_id length field.
|
||||
* @returns {string} Hex representation of session_id, or empty string if session_id not present.
|
||||
*/
|
||||
_readSessionID(input) {
|
||||
return readSizePrefixedBytesAsHex(input, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the cipher_suite field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ServerHello message, with position before cipher_suite field.
|
||||
* @returns {string} Hex represention of cipher_suite.
|
||||
*/
|
||||
_readCipherSuite(input) {
|
||||
return readBytesAsHex(input, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the compression_method field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ServerHello message, with position before compression_method field.
|
||||
* @returns {string} Hex represention of compression_method.
|
||||
*/
|
||||
_readCompressionMethod(input) {
|
||||
return readBytesAsHex(input, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the extensions field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ServerHello message, with position before extensions length field.
|
||||
* @returns {Object} Object representation of extensions field.
|
||||
*/
|
||||
_readExtensions(input) {
|
||||
const output = {};
|
||||
|
||||
output.length = input.readInt(2);
|
||||
if (!output.length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const extensions = new Stream(input.getBytes(output.length));
|
||||
if (extensions.length < output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
output.values = this._extensionsParser.parse(extensions);
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses TLS Handshake Hello Extensions.
|
||||
*/
|
||||
class ExtensionsParser {
|
||||
|
||||
/**
|
||||
* Parses a stream of TLS Handshake Hello Extensions.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing multiple raw Extensions, with position before first extension length field.
|
||||
* @returns {Object[]} Array of Object representations of Extensions contained within input.
|
||||
*/
|
||||
parse(input) {
|
||||
const output = [];
|
||||
|
||||
while (input.hasMore()) {
|
||||
const extension = this._readExtension(input);
|
||||
if (extension) {
|
||||
output.push(extension);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a single Extension from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a list of Extensions, with position before the length field of the next Extension.
|
||||
* @returns {Object} Object representation of Extension.
|
||||
*/
|
||||
_readExtension(input) {
|
||||
const output = {};
|
||||
|
||||
if (input.position + 4 > input.length) {
|
||||
input.moveTo(input.length);
|
||||
return null;
|
||||
}
|
||||
|
||||
output.type = "0x" + toHexFast(input.getBytes(2));
|
||||
output.length = input.readInt(2);
|
||||
if (!output.length) {
|
||||
return output;
|
||||
}
|
||||
|
||||
const value = input.getBytes(output.length);
|
||||
if (!value || value.length !== output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
if (value && value.length) {
|
||||
output.value = "0x" + toHexFast(value);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses TLS Handshake NewSessionTicket messages.
|
||||
*/
|
||||
class NewSessionTicketParser {
|
||||
|
||||
/**
|
||||
* Parses a single TLS Handshake NewSessionTicket message.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw NewSessionTicket message.
|
||||
* @returns {Object} Object representation of NewSessionTicket.
|
||||
*/
|
||||
parse(input) {
|
||||
return {
|
||||
ticketLifetimeHint: this._readTicketLifetimeHint(input),
|
||||
ticket: this._readTicket(input),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the ticket_lifetime_hint field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw NewSessionTicket message, with position before ticket_lifetime_hint field.
|
||||
* @returns {string} Lifetime hint, in seconds.
|
||||
*/
|
||||
_readTicketLifetimeHint(input) {
|
||||
if (input.position + 4 > input.length) {
|
||||
input.moveTo(input.length);
|
||||
return "";
|
||||
}
|
||||
|
||||
return input.readInt(4) + "s";
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the ticket field fromt the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw NewSessionTicket message, with position before ticket length field.
|
||||
* @returns {string} Hex representation of ticket.
|
||||
*/
|
||||
_readTicket(input) {
|
||||
return readSizePrefixedBytesAsHex(input, 2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses TLS Handshake Certificate messages.
|
||||
*/
|
||||
class CertificateParser {
|
||||
|
||||
/**
|
||||
* Parses a single TLS Handshake Certificate message.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw Certificate message.
|
||||
* @returns {Object} Object representation of Certificate.
|
||||
*/
|
||||
parse(input) {
|
||||
const output = {};
|
||||
|
||||
output.certificateList = this._readCertificateList(input);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the certificate_list field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw Certificate message, with position before certificate_list length field.
|
||||
* @returns {string[]} Array of strings, each containing a hex representation of a value within the certificate_list field.
|
||||
*/
|
||||
_readCertificateList(input) {
|
||||
const output = {};
|
||||
|
||||
if (input.position + 3 > input.length) {
|
||||
input.moveTo(input.length);
|
||||
return output;
|
||||
}
|
||||
|
||||
output.length = input.readInt(3);
|
||||
if (!output.length) {
|
||||
return output;
|
||||
}
|
||||
|
||||
const certificates = new Stream(input.getBytes(output.length));
|
||||
if (certificates.length < output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
output.values = [];
|
||||
|
||||
while (certificates.hasMore()) {
|
||||
const certificate = this._readCertificate(certificates);
|
||||
if (certificate) {
|
||||
output.values.push(certificate);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a single certificate from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a list of certificicates, with position before the length field of the next certificate.
|
||||
* @returns {string} Hex representation of certificate.
|
||||
*/
|
||||
_readCertificate(input) {
|
||||
return readSizePrefixedBytesAsHex(input, 3);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses TLS Handshake CertificateRequest messages.
|
||||
*/
|
||||
class CertificateRequestParser {
|
||||
|
||||
/**
|
||||
* Parses a single TLS Handshake CertificateRequest message.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw CertificateRequest message.
|
||||
* @return {Object} Object representation of CertificateRequest.
|
||||
*/
|
||||
parse(input) {
|
||||
const output = {};
|
||||
|
||||
output.certificateTypes = this._readCertificateTypes(input);
|
||||
output.supportedSignatureAlgorithms = this._readSupportedSignatureAlgorithms(input);
|
||||
|
||||
const certificateAuthorities = this._readCertificateAuthorities(input);
|
||||
if (certificateAuthorities.length) {
|
||||
output.certificateAuthorities = certificateAuthorities;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the certificate_types field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw CertificateRequest message, with position before certificate_types length field.
|
||||
* @return {string[]} Array of strings, each containing a hex representation of a value within the certificate_types field.
|
||||
*/
|
||||
_readCertificateTypes(input) {
|
||||
const output = {};
|
||||
|
||||
output.length = input.readInt(1);
|
||||
if (!output.length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const certificateTypes = new Stream(input.getBytes(output.length));
|
||||
if (certificateTypes.length < output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
output.values = [];
|
||||
|
||||
while (certificateTypes.hasMore()) {
|
||||
const certificateType = readBytesAsHex(certificateTypes, 1);
|
||||
if (certificateType) {
|
||||
output.values.push(certificateType);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the supported_signature_algorithms field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw CertificateRequest message, with position before supported_signature_algorithms length field.
|
||||
* @returns {string[]} Array of strings, each containing a hex representation of a value within the supported_signature_algorithms field.
|
||||
*/
|
||||
_readSupportedSignatureAlgorithms(input) {
|
||||
const output = {};
|
||||
|
||||
output.length = input.readInt(2);
|
||||
if (!output.length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const signatureAlgorithms = new Stream(input.getBytes(output.length));
|
||||
if (signatureAlgorithms.length < output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
output.values = [];
|
||||
|
||||
while (signatureAlgorithms.hasMore()) {
|
||||
const signatureAlgorithm = readBytesAsHex(signatureAlgorithms, 2);
|
||||
if (signatureAlgorithm) {
|
||||
output.values.push(signatureAlgorithm);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the certificate_authorities field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw CertificateRequest message, with position before certificate_authorities length field.
|
||||
* @returns {string[]} Array of strings, each containing a hex representation of a value within the certificate_authorities field.
|
||||
*/
|
||||
_readCertificateAuthorities(input) {
|
||||
const output = {};
|
||||
|
||||
output.length = input.readInt(2);
|
||||
if (!output.length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const certificateAuthorities = new Stream(input.getBytes(output.length));
|
||||
if (certificateAuthorities.length < output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
output.values = [];
|
||||
|
||||
while (certificateAuthorities.hasMore()) {
|
||||
const certificateAuthority = this._readCertificateAuthority(certificateAuthorities);
|
||||
if (certificateAuthority) {
|
||||
output.values.push(certificateAuthority);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a single certificate authority from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a list of raw certificate authorities, with position before the length field of the next certificate authority.
|
||||
* @returns {string} Hex representation of certificate authority.
|
||||
*/
|
||||
_readCertificateAuthority(input) {
|
||||
return readSizePrefixedBytesAsHex(input, 2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses TLS Handshake CertificateVerify messages.
|
||||
*/
|
||||
class CertificateVerifyParser {
|
||||
|
||||
/**
|
||||
* Parses a single CertificateVerify Message.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw CertificateVerify message.
|
||||
* @returns {Object} Object representation of CertificateVerify.
|
||||
*/
|
||||
parse(input) {
|
||||
return {
|
||||
algorithmHash: this._readAlgorithmHash(input),
|
||||
algorithmSignature: this._readAlgorithmSignature(input),
|
||||
signature: this._readSignature(input),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the algorithm.hash field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw CertificateVerify message, with position before algorithm.hash field.
|
||||
* @return {string} Hex representation of hash algorithm.
|
||||
*/
|
||||
_readAlgorithmHash(input) {
|
||||
return readBytesAsHex(input, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the algorithm.signature field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw CertificateVerify message, with position before algorithm.signature field.
|
||||
* @return {string} Hex representation of signature algorithm.
|
||||
*/
|
||||
_readAlgorithmSignature(input) {
|
||||
return readBytesAsHex(input, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the signature field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw CertificateVerify message, with position before signature field.
|
||||
* @return {string} Hex representation of signature.
|
||||
*/
|
||||
_readSignature(input) {
|
||||
return readSizePrefixedBytesAsHex(input, 2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the following size prefixed bytes from the provided Stream, and reuturn as a hex string.
|
||||
*
|
||||
* @param {Stream} input - Stream to read from.
|
||||
* @param {int} sizePrefixLength - Length of the size prefix field.
|
||||
* @returns {string} Hex representation of bytes read from Stream, empty string is returned if
|
||||
* field cannot be read in full.
|
||||
*/
|
||||
function readSizePrefixedBytesAsHex(input, sizePrefixLength) {
|
||||
const length = input.readInt(sizePrefixLength);
|
||||
if (!length) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return readBytesAsHex(input, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read n bytes from the provided Stream, and return as a hex string.
|
||||
*
|
||||
* @param {Stream} input - Stream to read from.
|
||||
* @param {int} n - Number of bytes to read.
|
||||
* @returns {string} Hex representation of bytes read from Stream, or empty string if field cannot
|
||||
* be read in full.
|
||||
*/
|
||||
function readBytesAsHex(input, n) {
|
||||
const bytes = input.getBytes(n);
|
||||
if (!bytes || bytes.length !== n) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return "0x" + toHexFast(bytes);
|
||||
}
|
||||
391
src/core/operations/ParseX509CRL.mjs
Normal file
391
src/core/operations/ParseX509CRL.mjs
Normal file
@@ -0,0 +1,391 @@
|
||||
/**
|
||||
* @author robinsandhu
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import r from "jsrsasign";
|
||||
import Operation from "../Operation.mjs";
|
||||
import { fromBase64 } from "../lib/Base64.mjs";
|
||||
import { toHex } from "../lib/Hex.mjs";
|
||||
import { formatDnObj } from "../lib/PublicKey.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* Parse X.509 CRL operation
|
||||
*/
|
||||
class ParseX509CRL extends Operation {
|
||||
|
||||
/**
|
||||
* ParseX509CRL constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Parse X.509 CRL";
|
||||
this.module = "PublicKey";
|
||||
this.description = "Parse Certificate Revocation List (CRL)";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Certificate_revocation_list";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Input format",
|
||||
"type": "option",
|
||||
"value": ["PEM", "DER Hex", "Base64", "Raw"]
|
||||
}
|
||||
];
|
||||
this.checks = [
|
||||
{
|
||||
"pattern": "^-+BEGIN X509 CRL-+\\r?\\n[\\da-z+/\\n\\r]+-+END X509 CRL-+\\r?\\n?$",
|
||||
"flags": "i",
|
||||
"args": ["PEM"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string} Human-readable description of a Certificate Revocation List (CRL).
|
||||
*/
|
||||
run(input, args) {
|
||||
if (!input.length) {
|
||||
return "No input";
|
||||
}
|
||||
|
||||
const inputFormat = args[0];
|
||||
|
||||
let undefinedInputFormat = false;
|
||||
try {
|
||||
switch (inputFormat) {
|
||||
case "DER Hex":
|
||||
input = input.replace(/\s/g, "").toLowerCase();
|
||||
break;
|
||||
case "PEM":
|
||||
break;
|
||||
case "Base64":
|
||||
input = toHex(fromBase64(input, null, "byteArray"), "");
|
||||
break;
|
||||
case "Raw":
|
||||
input = toHex(Utils.strToArrayBuffer(input), "");
|
||||
break;
|
||||
default:
|
||||
undefinedInputFormat = true;
|
||||
}
|
||||
} catch (e) {
|
||||
throw "Certificate load error (non-certificate input?)";
|
||||
}
|
||||
if (undefinedInputFormat) throw "Undefined input format";
|
||||
|
||||
const crl = new r.X509CRL(input);
|
||||
|
||||
let out = `Certificate Revocation List (CRL):
|
||||
Version: ${crl.getVersion() === null ? "1 (0x0)" : "2 (0x1)"}
|
||||
Signature Algorithm: ${crl.getSignatureAlgorithmField()}
|
||||
Issuer:\n${formatDnObj(crl.getIssuer(), 8)}
|
||||
Last Update: ${generalizedDateTimeToUTC(crl.getThisUpdate())}
|
||||
Next Update: ${generalizedDateTimeToUTC(crl.getNextUpdate())}\n`;
|
||||
|
||||
if (crl.getParam().ext !== undefined) {
|
||||
out += `\tCRL extensions:\n${formatCRLExtensions(crl.getParam().ext, 8)}\n`;
|
||||
}
|
||||
|
||||
out += `Revoked Certificates:\n${formatRevokedCertificates(crl.getRevCertArray(), 4)}
|
||||
Signature Value:\n${formatCRLSignature(crl.getSignatureValueHex(), 8)}`;
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generalized date time string to UTC.
|
||||
* @param {string} datetime
|
||||
* @returns UTC datetime string.
|
||||
*/
|
||||
function generalizedDateTimeToUTC(datetime) {
|
||||
// Ensure the string is in the correct format
|
||||
if (!/^\d{12,14}Z$/.test(datetime)) {
|
||||
throw new OperationError(`failed to format datetime string ${datetime}`);
|
||||
}
|
||||
|
||||
// Extract components
|
||||
let centuary = "20";
|
||||
if (datetime.length === 15) {
|
||||
centuary = datetime.substring(0, 2);
|
||||
datetime = datetime.slice(2);
|
||||
}
|
||||
const year = centuary + datetime.substring(0, 2);
|
||||
const month = datetime.substring(2, 4);
|
||||
const day = datetime.substring(4, 6);
|
||||
const hour = datetime.substring(6, 8);
|
||||
const minute = datetime.substring(8, 10);
|
||||
const second = datetime.substring(10, 12);
|
||||
|
||||
// Construct ISO 8601 format string
|
||||
const isoString = `${year}-${month}-${day}T${hour}:${minute}:${second}Z`;
|
||||
|
||||
// Parse using standard Date object
|
||||
const isoDateTime = new Date(isoString);
|
||||
|
||||
return isoDateTime.toUTCString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Format CRL extensions.
|
||||
* @param {r.ExtParam[] | undefined} extensions
|
||||
* @param {Number} indent
|
||||
* @returns Formatted string detailing CRL extensions.
|
||||
*/
|
||||
function formatCRLExtensions(extensions, indent) {
|
||||
if (Array.isArray(extensions) === false || extensions.length === 0) {
|
||||
return indentString(`No CRL extensions.`, indent);
|
||||
}
|
||||
|
||||
let out = ``;
|
||||
|
||||
extensions.sort((a, b) => {
|
||||
if (!Object.hasOwn(a, "extname") || !Object.hasOwn(b, "extname")) {
|
||||
return 0;
|
||||
}
|
||||
if (a.extname < b.extname) {
|
||||
return -1;
|
||||
} else if (a.extname === b.extname) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
|
||||
extensions.forEach((ext) => {
|
||||
if (!Object.hasOwn(ext, "extname")) {
|
||||
throw new OperationError(`CRL entry extension object missing 'extname' key: ${ext}`);
|
||||
}
|
||||
switch (ext.extname) {
|
||||
case "authorityKeyIdentifier":
|
||||
out += `X509v3 Authority Key Identifier:\n`;
|
||||
if (Object.hasOwn(ext, "kid")) {
|
||||
out += `\tkeyid:${colonDelimitedHexFormatString(ext.kid.hex.toUpperCase())}\n`;
|
||||
}
|
||||
if (Object.hasOwn(ext, "issuer")) {
|
||||
out += `\tDirName:${ext.issuer.str}\n`;
|
||||
}
|
||||
if (Object.hasOwn(ext, "sn")) {
|
||||
out += `\tserial:${colonDelimitedHexFormatString(ext.sn.hex.toUpperCase())}\n`;
|
||||
}
|
||||
break;
|
||||
case "cRLDistributionPoints":
|
||||
out += `X509v3 CRL Distribution Points:\n`;
|
||||
ext.array.forEach((distPoint) => {
|
||||
const fullName = `Full Name:\n${formatGeneralNames(distPoint.dpname.full, 4)}`;
|
||||
out += indentString(fullName, 4) + "\n";
|
||||
});
|
||||
break;
|
||||
case "cRLNumber":
|
||||
if (!Object.hasOwn(ext, "num")) {
|
||||
throw new OperationError(`'cRLNumber' CRL entry extension missing 'num' key: ${ext}`);
|
||||
}
|
||||
out += `X509v3 CRL Number:\n\t${ext.num.hex.toUpperCase()}\n`;
|
||||
break;
|
||||
case "issuerAltName":
|
||||
out += `X509v3 Issuer Alternative Name:\n${formatGeneralNames(ext.array, 4)}\n`;
|
||||
break;
|
||||
default:
|
||||
out += `${ext.extname}:\n`;
|
||||
out += `\tUnsupported CRL extension. Try openssl CLI.\n`;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return indentString(chop(out), indent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format general names array.
|
||||
* @param {Object[]} names
|
||||
* @returns Multi-line formatted string describing all supported general name types.
|
||||
*/
|
||||
function formatGeneralNames(names, indent) {
|
||||
let out = ``;
|
||||
|
||||
names.forEach((name) => {
|
||||
const key = Object.keys(name)[0];
|
||||
|
||||
switch (key) {
|
||||
case "ip":
|
||||
out += `IP:${name.ip}\n`;
|
||||
break;
|
||||
case "dns":
|
||||
out += `DNS:${name.dns}\n`;
|
||||
break;
|
||||
case "uri":
|
||||
out += `URI:${name.uri}\n`;
|
||||
break;
|
||||
case "rfc822":
|
||||
out += `EMAIL:${name.rfc822}\n`;
|
||||
break;
|
||||
case "dn":
|
||||
out += `DIR:${name.dn.str}\n`;
|
||||
break;
|
||||
case "other":
|
||||
out += `OtherName:${name.other.oid}::${Object.values(name.other.value)[0].str}\n`;
|
||||
break;
|
||||
default:
|
||||
out += `${key}: unsupported general name type`;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return indentString(chop(out), indent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Colon-delimited hex formatted output.
|
||||
* @param {string} hexString Hex String
|
||||
* @returns String representing input hex string with colon delimiter.
|
||||
*/
|
||||
function colonDelimitedHexFormatString(hexString) {
|
||||
if (hexString.length % 2 !== 0) {
|
||||
hexString = "0" + hexString;
|
||||
}
|
||||
|
||||
return chop(hexString.replace(/(..)/g, "$&:"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Format revoked certificates array
|
||||
* @param {r.RevokedCertificate[] | null} revokedCertificates
|
||||
* @param {Number} indent
|
||||
* @returns Multi-line formatted string output of revoked certificates array
|
||||
*/
|
||||
function formatRevokedCertificates(revokedCertificates, indent) {
|
||||
if (Array.isArray(revokedCertificates) === false || revokedCertificates.length === 0) {
|
||||
return indentString("No Revoked Certificates.", indent);
|
||||
}
|
||||
|
||||
let out=``;
|
||||
|
||||
revokedCertificates.forEach((revCert) => {
|
||||
if (!Object.hasOwn(revCert, "sn") || !Object.hasOwn(revCert, "date")) {
|
||||
throw new OperationError("invalid revoked certificate object, missing either serial number or date");
|
||||
}
|
||||
|
||||
out += `Serial Number: ${revCert.sn.hex.toUpperCase()}
|
||||
Revocation Date: ${generalizedDateTimeToUTC(revCert.date)}\n`;
|
||||
if (Object.hasOwn(revCert, "ext") && Array.isArray(revCert.ext) && revCert.ext.length !== 0) {
|
||||
out += `\tCRL entry extensions:\n${indentString(formatCRLEntryExtensions(revCert.ext), 2*indent)}\n`;
|
||||
}
|
||||
});
|
||||
|
||||
return indentString(chop(out), indent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format CRL entry extensions.
|
||||
* @param {Object[]} exts
|
||||
* @returns Formatted multi-line string describing CRL entry extensions.
|
||||
*/
|
||||
function formatCRLEntryExtensions(exts) {
|
||||
let out = ``;
|
||||
|
||||
const crlReasonCodeToReasonMessage = {
|
||||
0: "Unspecified",
|
||||
1: "Key Compromise",
|
||||
2: "CA Compromise",
|
||||
3: "Affiliation Changed",
|
||||
4: "Superseded",
|
||||
5: "Cessation Of Operation",
|
||||
6: "Certificate Hold",
|
||||
8: "Remove From CRL",
|
||||
9: "Privilege Withdrawn",
|
||||
10: "AA Compromise",
|
||||
};
|
||||
|
||||
const holdInstructionOIDToName = {
|
||||
"1.2.840.10040.2.1": "Hold Instruction None",
|
||||
"1.2.840.10040.2.2": "Hold Instruction Call Issuer",
|
||||
"1.2.840.10040.2.3": "Hold Instruction Reject",
|
||||
};
|
||||
|
||||
exts.forEach((ext) => {
|
||||
if (!Object.hasOwn(ext, "extname")) {
|
||||
throw new OperationError(`CRL entry extension object missing 'extname' key: ${ext}`);
|
||||
}
|
||||
switch (ext.extname) {
|
||||
case "cRLReason":
|
||||
if (!Object.hasOwn(ext, "code")) {
|
||||
throw new OperationError(`'cRLReason' CRL entry extension missing 'code' key: ${ext}`);
|
||||
}
|
||||
out += `X509v3 CRL Reason Code:
|
||||
${Object.hasOwn(crlReasonCodeToReasonMessage, ext.code) ? crlReasonCodeToReasonMessage[ext.code] : `invalid reason code: ${ext.code}`}\n`;
|
||||
break;
|
||||
case "2.5.29.23": // Hold instruction
|
||||
out += `Hold Instruction Code:\n\t${Object.hasOwn(holdInstructionOIDToName, ext.extn.oid) ? holdInstructionOIDToName[ext.extn.oid] : `${ext.extn.oid}: unknown hold instruction OID`}\n`;
|
||||
break;
|
||||
case "2.5.29.24": // Invalidity Date
|
||||
out += `Invalidity Date:\n\t${generalizedDateTimeToUTC(ext.extn.gentime.str)}\n`;
|
||||
break;
|
||||
default:
|
||||
out += `${ext.extname}:\n`;
|
||||
out += `\tUnsupported CRL entry extension. Try openssl CLI.\n`;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return chop(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format CRL signature.
|
||||
* @param {String} sigHex
|
||||
* @param {Number} indent
|
||||
* @returns String representing hex signature value formatted on multiple lines.
|
||||
*/
|
||||
function formatCRLSignature(sigHex, indent) {
|
||||
if (sigHex.length % 2 !== 0) {
|
||||
sigHex = "0" + sigHex;
|
||||
}
|
||||
|
||||
return indentString(formatMultiLine(chop(sigHex.replace(/(..)/g, "$&:"))), indent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format string onto multiple lines.
|
||||
* @param {string} longStr
|
||||
* @returns String as a multi-line string.
|
||||
*/
|
||||
function formatMultiLine(longStr) {
|
||||
const lines = [];
|
||||
|
||||
for (let remain = longStr ; remain !== "" ; remain = remain.substring(54)) {
|
||||
lines.push(remain.substring(0, 54));
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Indent a multi-line string by n spaces.
|
||||
* @param {string} input String
|
||||
* @param {number} spaces How many leading spaces
|
||||
* @returns Indented string.
|
||||
*/
|
||||
function indentString(input, spaces) {
|
||||
const indent = " ".repeat(spaces);
|
||||
return input.replace(/^/gm, indent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove last character from a string.
|
||||
* @param {string} s String
|
||||
* @returns Chopped string.
|
||||
*/
|
||||
function chop(s) {
|
||||
if (s.length < 1) {
|
||||
return s;
|
||||
}
|
||||
return s.substring(0, s.length - 1);
|
||||
}
|
||||
|
||||
export default ParseX509CRL;
|
||||
68
src/core/operations/PubKeyFromCert.mjs
Normal file
68
src/core/operations/PubKeyFromCert.mjs
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* @author cplussharp
|
||||
* @copyright Crown Copyright 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import r from "jsrsasign";
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* Public Key from Certificate operation
|
||||
*/
|
||||
class PubKeyFromCert extends Operation {
|
||||
|
||||
/**
|
||||
* PubKeyFromCert constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Public Key from Certificate";
|
||||
this.module = "PublicKey";
|
||||
this.description = "Extracts the Public Key from a Certificate.";
|
||||
this.infoURL = "https://en.wikipedia.org/wiki/X.509";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
this.checks = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
let output = "";
|
||||
let match;
|
||||
const regex = /-----BEGIN CERTIFICATE-----/g;
|
||||
while ((match = regex.exec(input)) !== null) {
|
||||
// find corresponding end tag
|
||||
const indexBase64 = match.index + match[0].length;
|
||||
const footer = "-----END CERTIFICATE-----";
|
||||
const indexFooter = input.indexOf(footer, indexBase64);
|
||||
if (indexFooter === -1) {
|
||||
throw new OperationError(`PEM footer '${footer}' not found`);
|
||||
}
|
||||
|
||||
const certPem = input.substring(match.index, indexFooter + footer.length);
|
||||
const cert = new r.X509();
|
||||
cert.readCertPEM(certPem);
|
||||
let pubKey;
|
||||
try {
|
||||
pubKey = cert.getPublicKey();
|
||||
} catch {
|
||||
throw new OperationError("Unsupported public key type");
|
||||
}
|
||||
const pubKeyPem = r.KEYUTIL.getPEM(pubKey);
|
||||
|
||||
// PEM ends with '\n', so a new key always starts on a new line
|
||||
output += pubKeyPem;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
export default PubKeyFromCert;
|
||||
82
src/core/operations/PubKeyFromPrivKey.mjs
Normal file
82
src/core/operations/PubKeyFromPrivKey.mjs
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* @author cplussharp
|
||||
* @copyright Crown Copyright 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import r from "jsrsasign";
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* Public Key from Private Key operation
|
||||
*/
|
||||
class PubKeyFromPrivKey extends Operation {
|
||||
|
||||
/**
|
||||
* PubKeyFromPrivKey constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Public Key from Private Key";
|
||||
this.module = "PublicKey";
|
||||
this.description = "Extracts the Public Key from a Private Key.";
|
||||
this.infoURL = "https://en.wikipedia.org/wiki/PKCS_8";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
this.checks = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
let output = "";
|
||||
let match;
|
||||
const regex = /-----BEGIN ((RSA |EC |DSA )?PRIVATE KEY)-----/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`);
|
||||
}
|
||||
|
||||
const privKeyPem = input.substring(match.index, indexFooter + footer.length);
|
||||
let privKey;
|
||||
try {
|
||||
privKey = r.KEYUTIL.getKey(privKeyPem);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Unsupported key type: ${err}`);
|
||||
}
|
||||
let pubKey;
|
||||
if (privKey.type && privKey.type === "EC") {
|
||||
pubKey = new r.KJUR.crypto.ECDSA({ curve: privKey.curve });
|
||||
pubKey.setPublicKeyHex(privKey.generatePublicKeyHex());
|
||||
} else if (privKey.type && privKey.type === "DSA") {
|
||||
if (!privKey.y) {
|
||||
throw new OperationError(`DSA Private Key in PKCS#8 is not supported`);
|
||||
}
|
||||
pubKey = new r.KJUR.crypto.DSA();
|
||||
pubKey.setPublic(privKey.p, privKey.q, privKey.g, privKey.y);
|
||||
} else if (privKey.n && privKey.e) {
|
||||
pubKey = new r.RSAKey();
|
||||
pubKey.setPublic(privKey.n, privKey.e);
|
||||
} else {
|
||||
throw new OperationError(`Unsupported key type`);
|
||||
}
|
||||
const pubKeyPem = r.KEYUTIL.getPEM(pubKey);
|
||||
|
||||
// PEM ends with '\n', so a new key always starts on a new line
|
||||
output += pubKeyPem;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
export default PubKeyFromPrivKey;
|
||||
144
src/core/operations/RAKE.mjs
Normal file
144
src/core/operations/RAKE.mjs
Normal file
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* @author sw5678
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
|
||||
/**
|
||||
* RAKE operation
|
||||
*/
|
||||
class RAKE extends Operation {
|
||||
|
||||
/**
|
||||
* RAKE constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "RAKE";
|
||||
this.module = "Default";
|
||||
this.description = [
|
||||
"Rapid Keyword Extraction (RAKE)",
|
||||
"<br><br>",
|
||||
"RAKE is a domain-independent keyword extraction algorithm in Natural Language Processing.",
|
||||
"<br><br>",
|
||||
"The list of stop words are from the NLTK python package",
|
||||
].join("\n");
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Word Delimiter (Regex)",
|
||||
type: "text",
|
||||
value: "\\s"
|
||||
},
|
||||
{
|
||||
name: "Sentence Delimiter (Regex)",
|
||||
type: "text",
|
||||
value: "\\.\\s|\\n"
|
||||
},
|
||||
{
|
||||
name: "Stop Words",
|
||||
type: "text",
|
||||
value: "i,me,my,myself,we,our,ours,ourselves,you,you're,you've,you'll,you'd,your,yours,yourself,yourselves,he,him,his,himself,she,she's,her,hers,herself,it,it's,its,itsef,they,them,their,theirs,themselves,what,which,who,whom,this,that,that'll,these,those,am,is,are,was,were,be,been,being,have,has,had,having,do,does',did,doing,a,an,the,and,but,if,or,because,as,until,while,of,at,by,for,with,about,against,between,into,through,during,before,after,above,below,to,from,up,down,in,out,on,off,over,under,again,further,then,once,here,there,when,where,why,how,all,any,both,each,few,more,most,other,some,such,no,nor,not,only,own,same,so,than,too,very,s,t,can,will,just,don,don't,should,should've,now,d,ll,m,o,re,ve,y,ain,aren,aren't,couldn,couldn't,didn,didn't,doesn,doesn't,hadn,hadn't,hasn,hasn't,haven,haven't,isn,isn't,ma,mightn,mightn't,mustn,mustn't,needn,needn't,shan,shan't,shouldn,shouldn't,wasn,wasn't,weren,weren't,won,won't,wouldn,wouldn't"
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
|
||||
// Get delimiter regexs
|
||||
const wordDelim = new RegExp(args[0], "g");
|
||||
const sentDelim = new RegExp(args[1], "g");
|
||||
|
||||
// Deduplicate the stop words and add the empty string
|
||||
const stopWords = args[2].toLowerCase().replace(/ /g, "").split(",").unique();
|
||||
stopWords.push("");
|
||||
|
||||
// Lower case input and remove start and ending whitespace
|
||||
input = input.toLowerCase().trim();
|
||||
|
||||
// Get tokens, token count, and phrases
|
||||
const tokens = [];
|
||||
const wordFrequencies = [];
|
||||
let phrases = [];
|
||||
|
||||
// Build up list of phrases and token counts
|
||||
const sentences = input.split(sentDelim);
|
||||
for (const sent of sentences) {
|
||||
|
||||
// Split sentence into words
|
||||
const splitSent = sent.split(wordDelim);
|
||||
let startIndex = 0;
|
||||
|
||||
for (let i = 0; i < splitSent.length; i++) {
|
||||
const token = splitSent[i];
|
||||
if (stopWords.includes(token)) {
|
||||
// If token is stop word then split to create phrase
|
||||
phrases.push(splitSent.slice(startIndex, i));
|
||||
startIndex = i + 1;
|
||||
} else {
|
||||
// If token is not a stop word add to the count of the list of words
|
||||
if (tokens.includes(token)) {
|
||||
wordFrequencies[tokens.indexOf(token)]+=1;
|
||||
} else {
|
||||
tokens.push(token);
|
||||
wordFrequencies.push(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
phrases.push(splitSent.slice(startIndex));
|
||||
}
|
||||
|
||||
// remove empty phrases
|
||||
phrases = phrases.filter(subArray => subArray.length > 0);
|
||||
|
||||
// Remove duplicate phrases
|
||||
phrases = phrases.unique();
|
||||
|
||||
// Generate word_degree_matrix and populate
|
||||
const wordDegreeMatrix = Array(tokens.length).fill().map(() => Array(tokens.length).fill(0));
|
||||
for (const phrase of phrases) {
|
||||
for (const word1 of phrase) {
|
||||
for (const word2 of phrase) {
|
||||
wordDegreeMatrix[tokens.indexOf(word1)][tokens.indexOf(word2)]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate degree score for each token
|
||||
const degreeScores = Array(tokens.length).fill(0);
|
||||
for (let i=0; i<tokens.length; i++) {
|
||||
let wordDegree = 0;
|
||||
for (let j=0; j<wordDegreeMatrix.length; j++) {
|
||||
wordDegree += wordDegreeMatrix[j][i];
|
||||
}
|
||||
degreeScores[i] = wordDegree / wordFrequencies[i];
|
||||
}
|
||||
|
||||
// Calculate score for each phrase
|
||||
const scores = phrases.map(function (phrase) {
|
||||
let score = 0;
|
||||
phrase.forEach(function (token) {
|
||||
score += degreeScores[tokens.indexOf(token)];
|
||||
});
|
||||
return new Array(score, phrase.join(" "));
|
||||
});
|
||||
scores.sort((a, b) => b[0] - a[0]);
|
||||
scores.unshift(new Array("Scores: ", "Keywords: "));
|
||||
|
||||
// Output works with the 'To Table' functionality already built into CC
|
||||
return scores.map(function (score) {
|
||||
return score.join(", ");
|
||||
}).join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
export default RAKE;
|
||||
@@ -60,7 +60,7 @@ class RSASign extends Operation {
|
||||
const privateKey = forge.pki.decryptRsaPrivateKey(key, password);
|
||||
// Generate message hash
|
||||
const md = MD_ALGORITHMS[mdAlgo].create();
|
||||
md.update(input, "utf8");
|
||||
md.update(input, "raw");
|
||||
// Sign message hash
|
||||
const sig = privateKey.sign(md);
|
||||
return sig;
|
||||
|
||||
@@ -8,6 +8,7 @@ import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import forge from "node-forge";
|
||||
import { MD_ALGORITHMS } from "../lib/RSA.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* RSA Verify operation
|
||||
@@ -37,6 +38,11 @@ class RSAVerify extends Operation {
|
||||
type: "text",
|
||||
value: ""
|
||||
},
|
||||
{
|
||||
name: "Message format",
|
||||
type: "option",
|
||||
value: ["Raw", "Hex", "Base64"]
|
||||
},
|
||||
{
|
||||
name: "Message Digest Algorithm",
|
||||
type: "option",
|
||||
@@ -51,7 +57,7 @@ class RSAVerify extends Operation {
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [pemKey, message, mdAlgo] = args;
|
||||
const [pemKey, message, format, mdAlgo] = args;
|
||||
if (pemKey.replace("-----BEGIN RSA PUBLIC KEY-----", "").length === 0) {
|
||||
throw new OperationError("Please enter a public key.");
|
||||
}
|
||||
@@ -60,7 +66,8 @@ class RSAVerify extends Operation {
|
||||
const pubKey = forge.pki.publicKeyFromPem(pemKey);
|
||||
// Generate message digest
|
||||
const md = MD_ALGORITHMS[mdAlgo].create();
|
||||
md.update(message, "utf8");
|
||||
const messageStr = Utils.convertToByteString(message, format);
|
||||
md.update(messageStr, "raw");
|
||||
// Compare signed message digest and generated message digest
|
||||
const result = pubKey.verify(md.digest().bytes(), input);
|
||||
return result ? "Verified OK" : "Verification Failure";
|
||||
|
||||
@@ -10,7 +10,7 @@ import Utils from "../Utils.mjs";
|
||||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { runHash } from "../lib/Hash.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Randomize Colour Palette operation
|
||||
@@ -48,7 +48,7 @@ class RandomizeColourPalette extends Operation {
|
||||
if (!isImage(input)) throw new OperationError("Please enter a valid image file.");
|
||||
|
||||
const seed = args[0] || (Math.random().toString().substr(2)),
|
||||
parsedImage = await jimp.read(input),
|
||||
parsedImage = await Jimp.read(input),
|
||||
width = parsedImage.bitmap.width,
|
||||
height = parsedImage.bitmap.height;
|
||||
|
||||
@@ -61,7 +61,7 @@ class RandomizeColourPalette extends Operation {
|
||||
parsedImage.setPixelColor(parseInt(rgbHex, 16), x, y);
|
||||
});
|
||||
|
||||
const imageBuffer = await parsedImage.getBufferAsync(jimp.AUTO);
|
||||
const imageBuffer = await parsedImage.getBufferAsync(Jimp.AUTO);
|
||||
|
||||
return new Uint8Array(imageBuffer).buffer;
|
||||
}
|
||||
|
||||
@@ -67,6 +67,10 @@ class RegularExpression extends Operation {
|
||||
name: "MAC address",
|
||||
value: "[A-Fa-f\\d]{2}(?:[:-][A-Fa-f\\d]{2}){5}"
|
||||
},
|
||||
{
|
||||
name: "UUID",
|
||||
value: "[0-9a-fA-F]{8}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{12}"
|
||||
},
|
||||
{
|
||||
name: "Date (yyyy-mm-dd)",
|
||||
value: "((?:19|20)\\d\\d)[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])"
|
||||
@@ -83,10 +87,6 @@ class RegularExpression extends Operation {
|
||||
name: "Strings",
|
||||
value: "[A-Za-z\\d/\\-:.,_$%\\x27\"()<>= !\\[\\]{}@]{4,}"
|
||||
},
|
||||
{
|
||||
name: "UUID (any version)",
|
||||
value: "[0-9a-fA-F]{8}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{12}"
|
||||
},
|
||||
],
|
||||
"target": 1
|
||||
},
|
||||
|
||||
@@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Resize Image operation
|
||||
@@ -80,11 +80,11 @@ class ResizeImage extends Operation {
|
||||
resizeAlg = args[4];
|
||||
|
||||
const resizeMap = {
|
||||
"Nearest Neighbour": jimp.RESIZE_NEAREST_NEIGHBOR,
|
||||
"Bilinear": jimp.RESIZE_BILINEAR,
|
||||
"Bicubic": jimp.RESIZE_BICUBIC,
|
||||
"Hermite": jimp.RESIZE_HERMITE,
|
||||
"Bezier": jimp.RESIZE_BEZIER
|
||||
"Nearest Neighbour": Jimp.RESIZE_NEAREST_NEIGHBOR,
|
||||
"Bilinear": Jimp.RESIZE_BILINEAR,
|
||||
"Bicubic": Jimp.RESIZE_BICUBIC,
|
||||
"Hermite": Jimp.RESIZE_HERMITE,
|
||||
"Bezier": Jimp.RESIZE_BEZIER
|
||||
};
|
||||
|
||||
if (!isImage(input)) {
|
||||
@@ -93,7 +93,7 @@ class ResizeImage extends Operation {
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
@@ -113,9 +113,9 @@ class ResizeImage extends Operation {
|
||||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
||||
@@ -20,7 +20,7 @@ class RisonDecode extends Operation {
|
||||
super();
|
||||
|
||||
this.name = "Rison Decode";
|
||||
this.module = "Default";
|
||||
this.module = "Encodings";
|
||||
this.description = "Rison, a data serialization format optimized for compactness in URIs. Rison is a slight variation of JSON that looks vastly superior after URI encoding. Rison still expresses exactly the same set of data structures as JSON, so data can be translated back and forth without loss or guesswork.";
|
||||
this.infoURL = "https://github.com/Nanonid/rison";
|
||||
this.inputType = "string";
|
||||
@@ -29,11 +29,7 @@ class RisonDecode extends Operation {
|
||||
{
|
||||
name: "Decode Option",
|
||||
type: "editableOption",
|
||||
value: [
|
||||
{ name: "Decode", value: "Decode", },
|
||||
{ name: "Decode Object", value: "Decode Object", },
|
||||
{ name: "Decode Array", value: "Decode Array", },
|
||||
]
|
||||
value: ["Decode", "Decode Object", "Decode Array"]
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -52,8 +48,9 @@ class RisonDecode extends Operation {
|
||||
return rison.decode_object(input);
|
||||
case "Decode Array":
|
||||
return rison.decode_array(input);
|
||||
default:
|
||||
throw new OperationError("Invalid Decode option");
|
||||
}
|
||||
throw new OperationError("Invalid Decode option");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class RisonEncode extends Operation {
|
||||
super();
|
||||
|
||||
this.name = "Rison Encode";
|
||||
this.module = "Default";
|
||||
this.module = "Encodings";
|
||||
this.description = "Rison, a data serialization format optimized for compactness in URIs. Rison is a slight variation of JSON that looks vastly superior after URI encoding. Rison still expresses exactly the same set of data structures as JSON, so data can be translated back and forth without loss or guesswork.";
|
||||
this.infoURL = "https://github.com/Nanonid/rison";
|
||||
this.inputType = "Object";
|
||||
@@ -28,13 +28,8 @@ class RisonEncode extends Operation {
|
||||
this.args = [
|
||||
{
|
||||
name: "Encode Option",
|
||||
type: "editableOption",
|
||||
value: [
|
||||
{ name: "Encode", value: "Encode", },
|
||||
{ name: "Encode Object", value: "Encode Object", },
|
||||
{ name: "Encode Array", value: "Encode Array", },
|
||||
{ name: "Encode URI", value: "Encode URI", }
|
||||
]
|
||||
type: "option",
|
||||
value: ["Encode", "Encode Object", "Encode Array", "Encode URI"]
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -55,8 +50,9 @@ class RisonEncode extends Operation {
|
||||
return rison.encode_array(input);
|
||||
case "Encode URI":
|
||||
return rison.encode_uri(input);
|
||||
default:
|
||||
throw new OperationError("Invalid encode option");
|
||||
}
|
||||
throw new OperationError("Invalid encode option");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Rotate Image operation
|
||||
@@ -52,7 +52,7 @@ class RotateImage extends Operation {
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
@@ -63,9 +63,9 @@ class RotateImage extends Operation {
|
||||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
||||
@@ -40,7 +40,7 @@ class Sigaba extends Operation {
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "1st cipher rotor intial value",
|
||||
name: "1st cipher rotor initial value",
|
||||
type: "option",
|
||||
value: LETTERS
|
||||
},
|
||||
@@ -56,7 +56,7 @@ class Sigaba extends Operation {
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "2nd cipher rotor intial value",
|
||||
name: "2nd cipher rotor initial value",
|
||||
type: "option",
|
||||
value: LETTERS
|
||||
},
|
||||
@@ -72,7 +72,7 @@ class Sigaba extends Operation {
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "3rd cipher rotor intial value",
|
||||
name: "3rd cipher rotor initial value",
|
||||
type: "option",
|
||||
value: LETTERS
|
||||
},
|
||||
@@ -88,7 +88,7 @@ class Sigaba extends Operation {
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "4th cipher rotor intial value",
|
||||
name: "4th cipher rotor initial value",
|
||||
type: "option",
|
||||
value: LETTERS
|
||||
},
|
||||
@@ -104,7 +104,7 @@ class Sigaba extends Operation {
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "5th cipher rotor intial value",
|
||||
name: "5th cipher rotor initial value",
|
||||
type: "option",
|
||||
value: LETTERS
|
||||
},
|
||||
@@ -120,7 +120,7 @@ class Sigaba extends Operation {
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "1st control rotor intial value",
|
||||
name: "1st control rotor initial value",
|
||||
type: "option",
|
||||
value: LETTERS
|
||||
},
|
||||
@@ -136,7 +136,7 @@ class Sigaba extends Operation {
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "2nd control rotor intial value",
|
||||
name: "2nd control rotor initial value",
|
||||
type: "option",
|
||||
value: LETTERS
|
||||
},
|
||||
@@ -152,7 +152,7 @@ class Sigaba extends Operation {
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "3rd control rotor intial value",
|
||||
name: "3rd control rotor initial value",
|
||||
type: "option",
|
||||
value: LETTERS
|
||||
},
|
||||
@@ -168,7 +168,7 @@ class Sigaba extends Operation {
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "4th control rotor intial value",
|
||||
name: "4th control rotor initial value",
|
||||
type: "option",
|
||||
value: LETTERS
|
||||
},
|
||||
@@ -184,7 +184,7 @@ class Sigaba extends Operation {
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "5th control rotor intial value",
|
||||
name: "5th control rotor initial value",
|
||||
type: "option",
|
||||
value: LETTERS
|
||||
},
|
||||
@@ -195,7 +195,7 @@ class Sigaba extends Operation {
|
||||
defaultIndex: 0
|
||||
},
|
||||
{
|
||||
name: "1st index rotor intial value",
|
||||
name: "1st index rotor initial value",
|
||||
type: "option",
|
||||
value: NUMBERS
|
||||
},
|
||||
@@ -206,7 +206,7 @@ class Sigaba extends Operation {
|
||||
defaultIndex: 0
|
||||
},
|
||||
{
|
||||
name: "2nd index rotor intial value",
|
||||
name: "2nd index rotor initial value",
|
||||
type: "option",
|
||||
value: NUMBERS
|
||||
},
|
||||
@@ -217,7 +217,7 @@ class Sigaba extends Operation {
|
||||
defaultIndex: 0
|
||||
},
|
||||
{
|
||||
name: "3rd index rotor intial value",
|
||||
name: "3rd index rotor initial value",
|
||||
type: "option",
|
||||
value: NUMBERS
|
||||
},
|
||||
@@ -228,7 +228,7 @@ class Sigaba extends Operation {
|
||||
defaultIndex: 0
|
||||
},
|
||||
{
|
||||
name: "4th index rotor intial value",
|
||||
name: "4th index rotor initial value",
|
||||
type: "option",
|
||||
value: NUMBERS
|
||||
},
|
||||
@@ -239,7 +239,7 @@ class Sigaba extends Operation {
|
||||
defaultIndex: 0
|
||||
},
|
||||
{
|
||||
name: "5th index rotor intial value",
|
||||
name: "5th index rotor initial value",
|
||||
type: "option",
|
||||
value: NUMBERS
|
||||
},
|
||||
|
||||
154
src/core/operations/Salsa20.mjs
Normal file
154
src/core/operations/Salsa20.mjs
Normal file
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* @author joostrijneveld [joost@joostrijneveld.nl]
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import { toHex } from "../lib/Hex.mjs";
|
||||
import { salsa20Block } from "../lib/Salsa20.mjs";
|
||||
|
||||
/**
|
||||
* Salsa20 operation
|
||||
*/
|
||||
class Salsa20 extends Operation {
|
||||
|
||||
/**
|
||||
* Salsa20 constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Salsa20";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Salsa20 is a stream cipher designed by Daniel J. Bernstein and submitted to the eSTREAM project; Salsa20/8 and Salsa20/12 are round-reduced variants. It is closely related to the ChaCha stream cipher.<br><br><b>Key:</b> Salsa20 uses a key of 16 or 32 bytes (128 or 256 bits).<br><br><b>Nonce:</b> Salsa20 uses a nonce of 8 bytes (64 bits).<br><br><b>Counter:</b> Salsa uses a counter of 8 bytes (64 bits). The counter starts at zero at the start of the keystream, and is incremented at every 64 bytes.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Salsa20";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Key",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "Nonce",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64", "Integer"]
|
||||
},
|
||||
{
|
||||
"name": "Counter",
|
||||
"type": "number",
|
||||
"value": 0,
|
||||
"min": 0
|
||||
},
|
||||
{
|
||||
"name": "Rounds",
|
||||
"type": "option",
|
||||
"value": ["20", "12", "8"]
|
||||
},
|
||||
{
|
||||
"name": "Input",
|
||||
"type": "option",
|
||||
"value": ["Hex", "Raw"]
|
||||
},
|
||||
{
|
||||
"name": "Output",
|
||||
"type": "option",
|
||||
"value": ["Raw", "Hex"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const key = Utils.convertToByteArray(args[0].string, args[0].option),
|
||||
nonceType = args[1].option,
|
||||
rounds = parseInt(args[3], 10),
|
||||
inputType = args[4],
|
||||
outputType = args[5];
|
||||
|
||||
if (key.length !== 16 && key.length !== 32) {
|
||||
throw new OperationError(`Invalid key length: ${key.length} bytes.
|
||||
|
||||
Salsa20 uses a key of 16 or 32 bytes (128 or 256 bits).`);
|
||||
}
|
||||
|
||||
let counter, nonce;
|
||||
if (nonceType === "Integer") {
|
||||
nonce = Utils.intToByteArray(parseInt(args[1].string, 10), 8, "little");
|
||||
} else {
|
||||
nonce = Utils.convertToByteArray(args[1].string, args[1].option);
|
||||
if (!(nonce.length === 8)) {
|
||||
throw new OperationError(`Invalid nonce length: ${nonce.length} bytes.
|
||||
|
||||
Salsa20 uses a nonce of 8 bytes (64 bits).`);
|
||||
}
|
||||
}
|
||||
counter = Utils.intToByteArray(args[2], 8, "little");
|
||||
|
||||
const output = [];
|
||||
input = Utils.convertToByteArray(input, inputType);
|
||||
|
||||
let counterAsInt = Utils.byteArrayToInt(counter, "little");
|
||||
for (let i = 0; i < input.length; i += 64) {
|
||||
counter = Utils.intToByteArray(counterAsInt, 8, "little");
|
||||
const stream = salsa20Block(key, nonce, counter, rounds);
|
||||
for (let j = 0; j < 64 && i + j < input.length; j++) {
|
||||
output.push(input[i + j] ^ stream[j]);
|
||||
}
|
||||
counterAsInt++;
|
||||
}
|
||||
|
||||
if (outputType === "Hex") {
|
||||
return toHex(output);
|
||||
} else {
|
||||
return Utils.arrayBufferToStr(Uint8Array.from(output).buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight Salsa20
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlight(pos, args) {
|
||||
const inputType = args[4],
|
||||
outputType = args[5];
|
||||
if (inputType === "Raw" && outputType === "Raw") {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight Salsa20 in reverse
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightReverse(pos, args) {
|
||||
const inputType = args[4],
|
||||
outputType = args[5];
|
||||
if (inputType === "Raw" && outputType === "Raw") {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Salsa20;
|
||||
@@ -10,7 +10,7 @@ import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { gaussianBlur } from "../lib/ImageManipulation.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Sharpen Image operation
|
||||
@@ -68,7 +68,7 @@ class SharpenImage extends Operation {
|
||||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
@@ -137,9 +137,9 @@ class SharpenImage extends Operation {
|
||||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import {isImage} from "../lib/FileType.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Split Colour Channels operation
|
||||
@@ -41,7 +41,7 @@ class SplitColourChannels extends Operation {
|
||||
// Make sure that the input is an image
|
||||
if (!isImage(input)) throw new OperationError("Invalid file type.");
|
||||
|
||||
const parsedImage = await jimp.read(Buffer.from(input));
|
||||
const parsedImage = await Jimp.read(Buffer.from(input));
|
||||
|
||||
const red = new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
@@ -51,7 +51,7 @@ class SplitColourChannels extends Operation {
|
||||
{apply: "blue", params: [-255]},
|
||||
{apply: "green", params: [-255]}
|
||||
])
|
||||
.getBufferAsync(jimp.MIME_PNG);
|
||||
.getBufferAsync(Jimp.MIME_PNG);
|
||||
resolve(new File([new Uint8Array((await split).values())], "red.png", {type: "image/png"}));
|
||||
} catch (err) {
|
||||
reject(new OperationError(`Could not split red channel: ${err}`));
|
||||
@@ -64,7 +64,7 @@ class SplitColourChannels extends Operation {
|
||||
.color([
|
||||
{apply: "red", params: [-255]},
|
||||
{apply: "blue", params: [-255]},
|
||||
]).getBufferAsync(jimp.MIME_PNG);
|
||||
]).getBufferAsync(Jimp.MIME_PNG);
|
||||
resolve(new File([new Uint8Array((await split).values())], "green.png", {type: "image/png"}));
|
||||
} catch (err) {
|
||||
reject(new OperationError(`Could not split green channel: ${err}`));
|
||||
@@ -77,7 +77,7 @@ class SplitColourChannels extends Operation {
|
||||
.color([
|
||||
{apply: "red", params: [-255]},
|
||||
{apply: "green", params: [-255]},
|
||||
]).getBufferAsync(jimp.MIME_PNG);
|
||||
]).getBufferAsync(Jimp.MIME_PNG);
|
||||
resolve(new File([new Uint8Array((await split).values())], "blue.png", {type: "image/png"}));
|
||||
} catch (err) {
|
||||
reject(new OperationError(`Could not split blue channel: ${err}`));
|
||||
|
||||
60
src/core/operations/StripTCPHeader.mjs
Normal file
60
src/core/operations/StripTCPHeader.mjs
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* @author c65722 []
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Stream from "../lib/Stream.mjs";
|
||||
|
||||
/**
|
||||
* Strip TCP header operation
|
||||
*/
|
||||
class StripTCPHeader extends Operation {
|
||||
|
||||
/**
|
||||
* StripTCPHeader constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Strip TCP header";
|
||||
this.module = "Default";
|
||||
this.description = "Strips the TCP header from a TCP segment, outputting the payload.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Transmission_Control_Protocol";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "ArrayBuffer";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
run(input, args) {
|
||||
const MIN_HEADER_LEN = 20;
|
||||
const DATA_OFFSET_OFFSET = 12;
|
||||
const DATA_OFFSET_LEN_BITS = 4;
|
||||
|
||||
const s = new Stream(new Uint8Array(input));
|
||||
if (s.length < MIN_HEADER_LEN) {
|
||||
throw new OperationError("Need at least 20 bytes for a TCP Header");
|
||||
}
|
||||
|
||||
s.moveTo(DATA_OFFSET_OFFSET);
|
||||
const dataOffsetWords = s.readBits(DATA_OFFSET_LEN_BITS);
|
||||
const dataOffsetBytes = dataOffsetWords * 4;
|
||||
if (s.length < dataOffsetBytes) {
|
||||
throw new OperationError("Input length is less than data offset");
|
||||
}
|
||||
|
||||
s.moveTo(dataOffsetBytes);
|
||||
|
||||
return s.getBytes().buffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default StripTCPHeader;
|
||||
51
src/core/operations/StripUDPHeader.mjs
Normal file
51
src/core/operations/StripUDPHeader.mjs
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @author c65722 []
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Stream from "../lib/Stream.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* Strip UDP header operation
|
||||
*/
|
||||
class StripUDPHeader extends Operation {
|
||||
|
||||
/**
|
||||
* StripUDPHeader constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Strip UDP header";
|
||||
this.module = "Default";
|
||||
this.description = "Strips the UDP header from a UDP datagram, outputting the payload.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/User_Datagram_Protocol";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "ArrayBuffer";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
run(input, args) {
|
||||
const HEADER_LEN = 8;
|
||||
|
||||
const s = new Stream(new Uint8Array(input));
|
||||
if (s.length < HEADER_LEN) {
|
||||
throw new OperationError("Need 8 bytes for a UDP Header");
|
||||
}
|
||||
|
||||
s.moveTo(HEADER_LEN);
|
||||
|
||||
return s.getBytes().buffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default StripUDPHeader;
|
||||
@@ -43,7 +43,7 @@ class ToBase58 extends Operation {
|
||||
run(input, args) {
|
||||
input = new Uint8Array(input);
|
||||
let alphabet = args[0] || ALPHABET_OPTIONS[0].value,
|
||||
result = [0];
|
||||
result = [];
|
||||
|
||||
alphabet = Utils.expandAlphRange(alphabet).join("");
|
||||
|
||||
@@ -60,11 +60,9 @@ class ToBase58 extends Operation {
|
||||
}
|
||||
|
||||
input.forEach(function(b) {
|
||||
let carry = (result[0] << 8) + b;
|
||||
result[0] = carry % 58;
|
||||
carry = (carry / 58) | 0;
|
||||
let carry = b;
|
||||
|
||||
for (let i = 1; i < result.length; i++) {
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
carry += result[i] << 8;
|
||||
result[i] = carry % 58;
|
||||
carry = (carry / 58) | 0;
|
||||
|
||||
80
src/core/operations/ToFloat.mjs
Normal file
80
src/core/operations/ToFloat.mjs
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* @author tcode2k16 [tcode2k16@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import ieee754 from "ieee754";
|
||||
import {DELIM_OPTIONS} from "../lib/Delim.mjs";
|
||||
|
||||
/**
|
||||
* To Float operation
|
||||
*/
|
||||
class ToFloat extends Operation {
|
||||
|
||||
/**
|
||||
* ToFloat constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "To Float";
|
||||
this.module = "Default";
|
||||
this.description = "Convert to IEEE754 Floating Point Numbers";
|
||||
this.infoURL = "https://wikipedia.org/wiki/IEEE_754";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Endianness",
|
||||
"type": "option",
|
||||
"value": [
|
||||
"Big Endian",
|
||||
"Little Endian"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Size",
|
||||
"type": "option",
|
||||
"value": [
|
||||
"Float (4 bytes)",
|
||||
"Double (8 bytes)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Delimiter",
|
||||
"type": "option",
|
||||
"value": DELIM_OPTIONS
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [endianness, size, delimiterName] = args;
|
||||
const delim = Utils.charRep(delimiterName || "Space");
|
||||
const byteSize = size === "Double (8 bytes)" ? 8 : 4;
|
||||
const isLE = endianness === "Little Endian";
|
||||
const mLen = byteSize === 4 ? 23 : 52;
|
||||
|
||||
if (input.length % byteSize !== 0) {
|
||||
throw new OperationError(`Input is not a multiple of ${byteSize}`);
|
||||
}
|
||||
|
||||
const output = [];
|
||||
for (let i = 0; i < input.length; i+=byteSize) {
|
||||
output.push(ieee754.read(input, i, isLE, mLen, byteSize));
|
||||
}
|
||||
return output.join(delim);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ToFloat;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user