mirror of
https://github.com/gchq/CyberChef.git
synced 2025-04-20 14:56:19 -04:00
Merge branch 'master' into jq
This commit is contained in:
commit
d602897221
221 changed files with 25637 additions and 14369 deletions
17
.cspell.json
Normal file
17
.cspell.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"version": "0.2",
|
||||||
|
"language": "en,en-gb",
|
||||||
|
"words": [],
|
||||||
|
"dictionaries": [
|
||||||
|
"npm",
|
||||||
|
"softwareTerms",
|
||||||
|
"node",
|
||||||
|
"html",
|
||||||
|
"css",
|
||||||
|
"bash",
|
||||||
|
"en-gb",
|
||||||
|
"misc"
|
||||||
|
],
|
||||||
|
"ignorePaths": ["package.json", "package-lock.json", "node_modules"]
|
||||||
|
}
|
||||||
|
|
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"
|
||||||
|
}
|
2
.dockerignore
Normal file
2
.dockerignore
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
node_modules
|
||||||
|
build
|
|
@ -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
|
- name: Install
|
||||||
run: |
|
run: |
|
||||||
|
export DETECT_CHROMEDRIVER_VERSION=true
|
||||||
npm install
|
npm install
|
||||||
npm run setheapsize
|
npm run setheapsize
|
||||||
|
|
||||||
|
|
15
.github/workflows/pull_requests.yml
vendored
15
.github/workflows/pull_requests.yml
vendored
|
@ -18,6 +18,7 @@ jobs:
|
||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
run: |
|
run: |
|
||||||
|
export DETECT_CHROMEDRIVER_VERSION=true
|
||||||
npm install
|
npm install
|
||||||
npm run setheapsize
|
npm run setheapsize
|
||||||
|
|
||||||
|
@ -33,6 +34,20 @@ jobs:
|
||||||
if: success()
|
if: success()
|
||||||
run: npx grunt prod
|
run: npx grunt prod
|
||||||
|
|
||||||
|
- name: Production Image Build
|
||||||
|
if: success()
|
||||||
|
id: build-image
|
||||||
|
uses: redhat-actions/buildah-build@v2
|
||||||
|
with:
|
||||||
|
# Not being uploaded to any registry, use a simple name to allow Buildah to build correctly.
|
||||||
|
image: cyberchef
|
||||||
|
containerfiles: ./Dockerfile
|
||||||
|
platforms: linux/amd64
|
||||||
|
oci: true
|
||||||
|
# Webpack seems to use a lot of open files, increase the max open file limit to accomodate.
|
||||||
|
extra-args: |
|
||||||
|
--ulimit nofile=10000
|
||||||
|
|
||||||
- name: UI Tests
|
- name: UI Tests
|
||||||
if: success()
|
if: success()
|
||||||
run: |
|
run: |
|
||||||
|
|
44
.github/workflows/releases.yml
vendored
44
.github/workflows/releases.yml
vendored
|
@ -6,6 +6,12 @@ on:
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
REGISTRY_USER: ${{ github.actor }}
|
||||||
|
REGISTRY_PASSWORD: ${{ github.token }}
|
||||||
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
main:
|
main:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -19,7 +25,7 @@ jobs:
|
||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
run: |
|
run: |
|
||||||
npm install
|
npm ci
|
||||||
npm run setheapsize
|
npm run setheapsize
|
||||||
|
|
||||||
- name: Lint
|
- name: Lint
|
||||||
|
@ -31,17 +37,38 @@ jobs:
|
||||||
npm run testnodeconsumer
|
npm run testnodeconsumer
|
||||||
|
|
||||||
- name: Production Build
|
- name: Production Build
|
||||||
if: success()
|
|
||||||
run: npx grunt prod
|
run: npx grunt prod
|
||||||
|
|
||||||
- name: UI Tests
|
- name: UI Tests
|
||||||
if: success()
|
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get install xvfb
|
sudo apt-get install xvfb
|
||||||
xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
|
xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
|
||||||
|
|
||||||
|
- name: Image Metadata
|
||||||
|
id: image-metadata
|
||||||
|
uses: docker/metadata-action@v4
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
tags: |
|
||||||
|
type=semver,pattern={{major}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
|
||||||
|
- name: Production Image Build
|
||||||
|
id: build-image
|
||||||
|
uses: redhat-actions/buildah-build@v2
|
||||||
|
with:
|
||||||
|
tags: ${{ steps.image-metadata.outputs.tags }}
|
||||||
|
labels: ${{ steps.image-metadata.outputs.labels }}
|
||||||
|
containerfiles: ./Dockerfile
|
||||||
|
platforms: linux/amd64
|
||||||
|
oci: true
|
||||||
|
# Webpack seems to use a lot of open files, increase the max open file limit to accomodate.
|
||||||
|
extra-args: |
|
||||||
|
--ulimit nofile=10000
|
||||||
|
|
||||||
|
|
||||||
- name: Upload Release Assets
|
- name: Upload Release Assets
|
||||||
if: success()
|
|
||||||
id: upload-release-assets
|
id: upload-release-assets
|
||||||
uses: svenstaro/upload-release-action@v2
|
uses: svenstaro/upload-release-action@v2
|
||||||
with:
|
with:
|
||||||
|
@ -53,7 +80,14 @@ jobs:
|
||||||
body: "See the [CHANGELOG](https://github.com/gchq/CyberChef/blob/master/CHANGELOG.md) and [commit messages](https://github.com/gchq/CyberChef/commits/master) for details."
|
body: "See the [CHANGELOG](https://github.com/gchq/CyberChef/blob/master/CHANGELOG.md) and [commit messages](https://github.com/gchq/CyberChef/commits/master) for details."
|
||||||
|
|
||||||
- name: Publish to NPM
|
- name: Publish to NPM
|
||||||
if: success()
|
|
||||||
uses: JS-DevTools/npm-publish@v1
|
uses: JS-DevTools/npm-publish@v1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.NPM_TOKEN }}
|
token: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
||||||
|
- name: Publish to GHCR
|
||||||
|
uses: redhat-actions/push-to-registry@v2
|
||||||
|
with:
|
||||||
|
tags: ${{ steps.build-image.outputs.tags }}
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ env.REGISTRY_USER }}
|
||||||
|
password: ${{ env.REGISTRY_PASSWORD }}
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,6 +3,7 @@ npm-debug.log
|
||||||
travis.log
|
travis.log
|
||||||
build
|
build
|
||||||
.vscode
|
.vscode
|
||||||
|
.idea
|
||||||
.*.swp
|
.*.swp
|
||||||
src/core/config/modules/*
|
src/core/config/modules/*
|
||||||
src/core/config/OperationConfig.json
|
src/core/config/OperationConfig.json
|
||||||
|
|
136
CHANGELOG.md
136
CHANGELOG.md
|
@ -13,6 +13,74 @@ All major and minor version changes will be documented in this file. Details of
|
||||||
|
|
||||||
## Details
|
## 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]
|
||||||
|
|
||||||
|
### [10.7.0] - 2024-02-09
|
||||||
|
- Added 'File Tree' operation [@sw5678] | [#1667]
|
||||||
|
- Added 'RISON' operation [@sg5506844] | [#1555]
|
||||||
|
- Added 'MurmurHash3' operation [@AliceGrey] | [#1694]
|
||||||
|
|
||||||
|
### [10.6.0] - 2024-02-03
|
||||||
|
- Updated 'Forensics Wiki' URLs to new domain [@a3957273] | [#1703]
|
||||||
|
- Added 'LZNT1 Decompress' operation [@0xThiebaut] | [#1675]
|
||||||
|
- Updated 'Regex Expression' UUID matcher [@cnotin] | [#1678]
|
||||||
|
- Removed duplicate 'hover' message within baking info [@KevinSJ] | [#1541]
|
||||||
|
|
||||||
### [10.5.0] - 2023-07-14
|
### [10.5.0] - 2023-07-14
|
||||||
- Added GOST Encrypt, Decrypt, Sign, Verify, Key Wrap, and Key Unwrap operations [@n1474335] | [#592]
|
- Added GOST Encrypt, Decrypt, Sign, Verify, Key Wrap, and Key Unwrap operations [@n1474335] | [#592]
|
||||||
|
|
||||||
|
@ -372,8 +440,20 @@ All major and minor version changes will be documented in this file. Details of
|
||||||
## [4.0.0] - 2016-11-28
|
## [4.0.0] - 2016-11-28
|
||||||
- Initial open source commit [@n1474335] | [b1d73a72](https://github.com/gchq/CyberChef/commit/b1d73a725dc7ab9fb7eb789296efd2b7e4b08306)
|
- 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
|
||||||
[10.5.0]: https://github.com/gchq/CyberChef/releases/tag/v10.5.0
|
[10.5.0]: https://github.com/gchq/CyberChef/releases/tag/v10.5.0
|
||||||
[10.4.0]: https://github.com/gchq/CyberChef/releases/tag/v10.4.0
|
[10.4.0]: https://github.com/gchq/CyberChef/releases/tag/v10.4.0
|
||||||
[10.3.0]: https://github.com/gchq/CyberChef/releases/tag/v10.3.0
|
[10.3.0]: https://github.com/gchq/CyberChef/releases/tag/v10.3.0
|
||||||
|
@ -528,6 +608,29 @@ All major and minor version changes will be documented in this file. Details of
|
||||||
[@joostrijneveld]: https://github.com/joostrijneveld
|
[@joostrijneveld]: https://github.com/joostrijneveld
|
||||||
[@Xenonym]: https://github.com/Xenonym
|
[@Xenonym]: https://github.com/Xenonym
|
||||||
[@gchq77703]: https://github.com/gchq77703
|
[@gchq77703]: https://github.com/gchq77703
|
||||||
|
[@a3957273]: https://github.com/a3957273
|
||||||
|
[@0xThiebaut]: https://github.com/0xThiebaut
|
||||||
|
[@cnotin]: https://github.com/cnotin
|
||||||
|
[@KevinSJ]: https://github.com/KevinSJ
|
||||||
|
[@sw5678]: https://github.com/sw5678
|
||||||
|
[@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
|
[8ad18b]: https://github.com/gchq/CyberChef/commit/8ad18bc7db6d9ff184ba3518686293a7685bf7b7
|
||||||
[9a33498]: https://github.com/gchq/CyberChef/commit/9a33498fed26a8df9c9f35f39a78a174bf50a513
|
[9a33498]: https://github.com/gchq/CyberChef/commit/9a33498fed26a8df9c9f35f39a78a174bf50a513
|
||||||
|
@ -537,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
|
[a895d1d]: https://github.com/gchq/CyberChef/commit/a895d1d82a2f92d440a0c5eca2bc7c898107b737
|
||||||
[31a7f83]: https://github.com/gchq/CyberChef/commit/31a7f83b82e78927f89689f323fcb9185144d6ff
|
[31a7f83]: https://github.com/gchq/CyberChef/commit/31a7f83b82e78927f89689f323fcb9185144d6ff
|
||||||
[760eff4]: https://github.com/gchq/CyberChef/commit/760eff49b5307aaa3104c5e5b437ffe62299acd1
|
[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
|
[#95]: https://github.com/gchq/CyberChef/pull/299
|
||||||
[#173]: https://github.com/gchq/CyberChef/pull/173
|
[#173]: https://github.com/gchq/CyberChef/pull/173
|
||||||
|
@ -646,4 +751,31 @@ All major and minor version changes will be documented in this file. Details of
|
||||||
[#661]: https://github.com/gchq/CyberChef/pull/661
|
[#661]: https://github.com/gchq/CyberChef/pull/661
|
||||||
[#493]: https://github.com/gchq/CyberChef/pull/493
|
[#493]: https://github.com/gchq/CyberChef/pull/493
|
||||||
[#592]: https://github.com/gchq/CyberChef/issues/592
|
[#592]: https://github.com/gchq/CyberChef/issues/592
|
||||||
|
[#1703]: https://github.com/gchq/CyberChef/issues/1703
|
||||||
|
[#1675]: https://github.com/gchq/CyberChef/issues/1675
|
||||||
|
[#1678]: https://github.com/gchq/CyberChef/issues/1678
|
||||||
|
[#1541]: https://github.com/gchq/CyberChef/issues/1541
|
||||||
|
[#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/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
|
||||||
|
|
||||||
|
|
9
Dockerfile
Normal file
9
Dockerfile
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
FROM node:18-alpine AS build
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
RUN npm ci
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM nginx:1.25-alpine3.18 AS cyberchef
|
||||||
|
|
||||||
|
COPY --from=build ./build/prod /usr/share/nginx/html/
|
18
Gruntfile.js
18
Gruntfile.js
|
@ -86,10 +86,12 @@ module.exports = function (grunt) {
|
||||||
|
|
||||||
|
|
||||||
// Project configuration
|
// 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"),
|
pkg = grunt.file.readJSON("package.json"),
|
||||||
webpackConfig = require("./webpack.config.js"),
|
webpackConfig = require("./webpack.config.js"),
|
||||||
BUILD_CONSTANTS = {
|
BUILD_CONSTANTS = {
|
||||||
|
COMPILE_YEAR: JSON.stringify(compileYear),
|
||||||
COMPILE_TIME: JSON.stringify(compileTime),
|
COMPILE_TIME: JSON.stringify(compileTime),
|
||||||
COMPILE_MSG: JSON.stringify(grunt.option("compile-msg") || grunt.option("msg") || ""),
|
COMPILE_MSG: JSON.stringify(grunt.option("compile-msg") || grunt.option("msg") || ""),
|
||||||
PKG_VERSION: JSON.stringify(pkg.version),
|
PKG_VERSION: JSON.stringify(pkg.version),
|
||||||
|
@ -125,6 +127,7 @@ module.exports = function (grunt) {
|
||||||
filename: "index.html",
|
filename: "index.html",
|
||||||
template: "./src/web/html/index.html",
|
template: "./src/web/html/index.html",
|
||||||
chunks: ["main"],
|
chunks: ["main"],
|
||||||
|
compileYear: compileYear,
|
||||||
compileTime: compileTime,
|
compileTime: compileTime,
|
||||||
version: pkg.version,
|
version: pkg.version,
|
||||||
minify: {
|
minify: {
|
||||||
|
@ -227,6 +230,7 @@ module.exports = function (grunt) {
|
||||||
filename: "index.html",
|
filename: "index.html",
|
||||||
template: "./src/web/html/index.html",
|
template: "./src/web/html/index.html",
|
||||||
chunks: ["main"],
|
chunks: ["main"],
|
||||||
|
compileYear: compileYear,
|
||||||
compileTime: compileTime,
|
compileTime: compileTime,
|
||||||
version: pkg.version,
|
version: pkg.version,
|
||||||
})
|
})
|
||||||
|
@ -427,6 +431,18 @@ module.exports = function (grunt) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
stdout: false
|
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
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
20
README.md
20
README.md
|
@ -20,6 +20,22 @@ Cryptographic operations in CyberChef should not be relied upon to provide secur
|
||||||
|
|
||||||
[A live demo can be found here][1] - have fun!
|
[A live demo can be found here][1] - have fun!
|
||||||
|
|
||||||
|
## Containers
|
||||||
|
|
||||||
|
If you would like to try out CyberChef locally you can either build it yourself:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build --tag cyberchef --ulimit nofile=10000 .
|
||||||
|
docker run -it -p 8080:80 cyberchef
|
||||||
|
```
|
||||||
|
|
||||||
|
Or you can use our image directly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -it -p 8080:80 ghcr.io/gchq/cyberchef:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
This image is built and published through our [GitHub Workflows](.github/workflows/releases.yml)
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
|
@ -89,14 +105,14 @@ CyberChef is built to support
|
||||||
|
|
||||||
## Node.js support
|
## Node.js support
|
||||||
|
|
||||||
CyberChef is built to fully support Node.js `v16`. For more information, see the Node API page in the project [wiki pages](https://github.com/gchq/CyberChef/wiki)
|
CyberChef is built to fully support Node.js `v16`. For more information, see the ["Node API" wiki page](https://github.com/gchq/CyberChef/wiki/Node-API)
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Contributing a new operation to CyberChef is super easy! The quickstart script will walk you through the process. If you can write basic JavaScript, you can write a CyberChef operation.
|
Contributing a new operation to CyberChef is super easy! The quickstart script will walk you through the process. If you can write basic JavaScript, you can write a CyberChef operation.
|
||||||
|
|
||||||
An installation walkthrough, how-to guides for adding new operations and themes, descriptions of the repository structure, available data types and coding conventions can all be found in the project [wiki pages](https://github.com/gchq/CyberChef/wiki).
|
An installation walkthrough, how-to guides for adding new operations and themes, descriptions of the repository structure, available data types and coding conventions can all be found in the ["Contributing" wiki page](https://github.com/gchq/CyberChef/wiki/Contributing).
|
||||||
|
|
||||||
- Push your changes to your fork.
|
- Push your changes to your fork.
|
||||||
- Submit a pull request. If you are doing this for the first time, you will be prompted to sign the [GCHQ Contributor Licence Agreement](https://cla-assistant.io/gchq/CyberChef) via the CLA assistant on the pull request. This will also ask whether you are happy for GCHQ to contact you about a token of thanks for your contribution, or about job opportunities at GCHQ.
|
- Submit a pull request. If you are doing this for the first time, you will be prompted to sign the [GCHQ Contributor Licence Agreement](https://cla-assistant.io/gchq/CyberChef) via the CLA assistant on the pull request. This will also ask whether you are happy for GCHQ to contact you about a token of thanks for your contribution, or about job opportunities at GCHQ.
|
||||||
|
|
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"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
22631
package-lock.json
generated
22631
package-lock.json
generated
File diff suppressed because it is too large
Load diff
136
package.json
136
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "cyberchef",
|
"name": "cyberchef",
|
||||||
"version": "10.5.2",
|
"version": "10.19.4",
|
||||||
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
|
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
|
||||||
"author": "n1474335 <n1474335@gmail.com>",
|
"author": "n1474335 <n1474335@gmail.com>",
|
||||||
"homepage": "https://gchq.github.io/CyberChef",
|
"homepage": "https://gchq.github.io/CyberChef",
|
||||||
|
@ -39,55 +39,59 @@
|
||||||
"node >= 16"
|
"node >= 16"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.21.0",
|
"@babel/core": "^7.24.7",
|
||||||
"@babel/eslint-parser": "^7.19.1",
|
"@babel/eslint-parser": "^7.24.7",
|
||||||
"@babel/plugin-syntax-import-assertions": "^7.20.0",
|
"@babel/plugin-syntax-import-assertions": "^7.24.7",
|
||||||
"@babel/plugin-transform-runtime": "^7.21.0",
|
"@babel/plugin-transform-runtime": "^7.24.7",
|
||||||
"@babel/preset-env": "^7.20.2",
|
"@babel/preset-env": "^7.24.7",
|
||||||
"@babel/runtime": "^7.21.0",
|
"@babel/runtime": "^7.24.7",
|
||||||
"@codemirror/commands": "^6.2.1",
|
"@codemirror/commands": "^6.6.0",
|
||||||
"@codemirror/language": "^6.6.0",
|
"@codemirror/language": "^6.10.2",
|
||||||
"@codemirror/search": "^6.2.3",
|
"@codemirror/search": "^6.5.6",
|
||||||
"@codemirror/state": "^6.2.0",
|
"@codemirror/state": "^6.4.1",
|
||||||
"@codemirror/view": "^6.9.2",
|
"@codemirror/view": "^6.28.0",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.19",
|
||||||
"babel-loader": "^9.1.2",
|
"babel-loader": "^9.1.3",
|
||||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||||
"babel-plugin-transform-builtin-extend": "1.1.2",
|
"babel-plugin-transform-builtin-extend": "1.1.2",
|
||||||
"base64-loader": "^1.0.0",
|
"base64-loader": "^1.0.0",
|
||||||
"chromedriver": "^114.0.2",
|
"chromedriver": "^130.0.0",
|
||||||
"cli-progress": "^3.12.0",
|
"cli-progress": "^3.12.0",
|
||||||
"colors": "^1.4.0",
|
"colors": "^1.4.0",
|
||||||
"copy-webpack-plugin": "^11.0.0",
|
"compression-webpack-plugin": "^11.1.0",
|
||||||
"core-js": "^3.29.0",
|
"copy-webpack-plugin": "^12.0.2",
|
||||||
"css-loader": "6.7.3",
|
"core-js": "^3.37.1",
|
||||||
"eslint": "^8.35.0",
|
"cspell": "^8.17.3",
|
||||||
|
"css-loader": "7.1.2",
|
||||||
|
"eslint": "^9.4.0",
|
||||||
|
"eslint-plugin-jsdoc": "^48.2.9",
|
||||||
|
"globals": "^15.4.0",
|
||||||
"grunt": "^1.6.1",
|
"grunt": "^1.6.1",
|
||||||
"grunt-chmod": "~1.1.1",
|
"grunt-chmod": "~1.1.1",
|
||||||
"grunt-concurrent": "^3.0.0",
|
"grunt-concurrent": "^3.0.0",
|
||||||
"grunt-contrib-clean": "~2.0.1",
|
"grunt-contrib-clean": "~2.0.1",
|
||||||
"grunt-contrib-connect": "^3.0.0",
|
"grunt-contrib-connect": "^4.0.0",
|
||||||
"grunt-contrib-copy": "~1.0.0",
|
"grunt-contrib-copy": "~1.0.0",
|
||||||
"grunt-contrib-watch": "^1.1.0",
|
"grunt-contrib-watch": "^1.1.0",
|
||||||
"grunt-eslint": "^24.0.1",
|
"grunt-eslint": "^25.0.0",
|
||||||
"grunt-exec": "~3.0.0",
|
"grunt-exec": "~3.0.0",
|
||||||
"grunt-webpack": "^5.0.0",
|
"grunt-webpack": "^6.0.0",
|
||||||
"grunt-zip": "^0.20.0",
|
"grunt-zip": "^1.0.0",
|
||||||
"html-webpack-plugin": "^5.5.0",
|
"html-webpack-plugin": "^5.6.0",
|
||||||
"imports-loader": "^4.0.1",
|
"imports-loader": "^5.0.0",
|
||||||
"mini-css-extract-plugin": "2.7.3",
|
"mini-css-extract-plugin": "2.9.0",
|
||||||
"modify-source-webpack-plugin": "^3.0.0",
|
"modify-source-webpack-plugin": "^4.1.0",
|
||||||
"nightwatch": "^2.6.16",
|
"nightwatch": "^3.6.3",
|
||||||
"postcss": "^8.4.21",
|
"postcss": "^8.4.38",
|
||||||
"postcss-css-variables": "^0.18.0",
|
"postcss-css-variables": "^0.19.0",
|
||||||
"postcss-import": "^15.1.0",
|
"postcss-import": "^16.1.0",
|
||||||
"postcss-loader": "^7.0.2",
|
"postcss-loader": "^8.1.1",
|
||||||
"prompt": "^1.3.0",
|
"prompt": "^1.3.0",
|
||||||
"sitemap": "^7.1.1",
|
"sitemap": "^8.0.0",
|
||||||
"terser": "^5.16.6",
|
"terser": "^5.31.1",
|
||||||
"webpack": "^5.76.0",
|
"webpack": "^5.91.0",
|
||||||
"webpack-bundle-analyzer": "^4.8.0",
|
"webpack-bundle-analyzer": "^4.10.2",
|
||||||
"webpack-dev-server": "4.11.1",
|
"webpack-dev-server": "5.0.4",
|
||||||
"webpack-node-externals": "^3.0.0",
|
"webpack-node-externals": "^3.0.0",
|
||||||
"worker-loader": "^3.0.8"
|
"worker-loader": "^3.0.8"
|
||||||
},
|
},
|
||||||
|
@ -96,11 +100,12 @@
|
||||||
"@babel/polyfill": "^7.12.1",
|
"@babel/polyfill": "^7.12.1",
|
||||||
"@blu3r4y/lzma": "^2.3.3",
|
"@blu3r4y/lzma": "^2.3.3",
|
||||||
"@wavesenterprise/crypto-gost-js": "^2.1.0-RC1",
|
"@wavesenterprise/crypto-gost-js": "^2.1.0-RC1",
|
||||||
|
"@xmldom/xmldom": "^0.8.10",
|
||||||
"argon2-browser": "^1.18.0",
|
"argon2-browser": "^1.18.0",
|
||||||
"arrive": "^2.4.1",
|
"arrive": "^2.4.1",
|
||||||
"avsc": "^5.7.7",
|
"avsc": "^5.7.7",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"bignumber.js": "^9.1.1",
|
"bignumber.js": "^9.1.2",
|
||||||
"blakejs": "^1.2.1",
|
"blakejs": "^1.2.1",
|
||||||
"bootstrap": "4.6.2",
|
"bootstrap": "4.6.2",
|
||||||
"bootstrap-colorpicker": "^3.4.0",
|
"bootstrap-colorpicker": "^3.4.0",
|
||||||
|
@ -108,46 +113,48 @@
|
||||||
"browserify-zlib": "^0.2.0",
|
"browserify-zlib": "^0.2.0",
|
||||||
"bson": "^4.7.2",
|
"bson": "^4.7.2",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"cbor": "8.1.0",
|
"cbor": "9.0.2",
|
||||||
"chi-squared": "^1.1.0",
|
"chi-squared": "^1.1.0",
|
||||||
"codepage": "^1.15.0",
|
"codepage": "^1.15.0",
|
||||||
"crypto-api": "^0.8.5",
|
"crypto-api": "^0.8.5",
|
||||||
"crypto-browserify": "^3.12.0",
|
"crypto-browserify": "^3.12.0",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.2.0",
|
||||||
"ctph.js": "0.0.5",
|
"ctph.js": "0.0.5",
|
||||||
"d3": "7.8.2",
|
"d3": "7.9.0",
|
||||||
"d3-hexbin": "^0.2.2",
|
"d3-hexbin": "^0.2.2",
|
||||||
"diff": "^5.1.0",
|
"diff": "^5.2.0",
|
||||||
"es6-promisify": "^7.0.0",
|
"es6-promisify": "^7.0.0",
|
||||||
"escodegen": "^2.0.0",
|
"escodegen": "^2.1.0",
|
||||||
"esprima": "^4.0.1",
|
"esprima": "^4.0.1",
|
||||||
"exif-parser": "^0.1.12",
|
"exif-parser": "^0.1.12",
|
||||||
|
"fernet": "^0.4.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"flat": "^5.0.2",
|
"flat": "^6.0.1",
|
||||||
"geodesy": "1.1.3",
|
"geodesy": "1.1.3",
|
||||||
"highlight.js": "^11.7.0",
|
"highlight.js": "^11.9.0",
|
||||||
"jimp": "^0.16.13",
|
"ieee754": "^1.2.1",
|
||||||
"jq-web": "^0.5.1",
|
"jimp": "^0.22.12",
|
||||||
"jquery": "3.6.4",
|
"jq-web": "^0.6.1",
|
||||||
|
"jquery": "3.7.1",
|
||||||
"js-crc": "^0.2.0",
|
"js-crc": "^0.2.0",
|
||||||
"js-sha3": "^0.8.0",
|
"js-sha3": "^0.9.3",
|
||||||
"jsesc": "^3.0.2",
|
"jsesc": "^3.0.2",
|
||||||
"json5": "^2.2.3",
|
"json5": "^2.2.3",
|
||||||
"jsonpath-plus": "^7.2.0",
|
"jsonpath-plus": "^9.0.0",
|
||||||
"jsonwebtoken": "8.5.1",
|
"jsonwebtoken": "8.5.1",
|
||||||
"jsqr": "^1.4.0",
|
"jsqr": "^1.4.0",
|
||||||
"jsrsasign": "^10.6.1",
|
"jsrsasign": "^11.1.0",
|
||||||
"kbpgp": "2.1.15",
|
"kbpgp": "2.1.15",
|
||||||
"libbzip2-wasm": "0.0.4",
|
"libbzip2-wasm": "0.0.4",
|
||||||
"libyara-wasm": "^1.2.1",
|
"libyara-wasm": "^1.2.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"loglevel": "^1.8.1",
|
"loglevel": "^1.9.1",
|
||||||
"loglevel-message-prefix": "^3.0.0",
|
"loglevel-message-prefix": "^3.0.0",
|
||||||
"lz-string": "^1.5.0",
|
"lz-string": "^1.5.0",
|
||||||
"lz4js": "^0.2.0",
|
"lz4js": "^0.2.0",
|
||||||
"markdown-it": "^13.0.1",
|
"markdown-it": "^14.1.0",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.30.1",
|
||||||
"moment-timezone": "^0.5.41",
|
"moment-timezone": "^0.5.45",
|
||||||
"ngeohash": "^0.6.3",
|
"ngeohash": "^0.6.3",
|
||||||
"node-forge": "^1.3.1",
|
"node-forge": "^1.3.1",
|
||||||
"node-md6": "^0.1.0",
|
"node-md6": "^0.1.0",
|
||||||
|
@ -155,26 +162,26 @@
|
||||||
"notepack.io": "^3.0.1",
|
"notepack.io": "^3.0.1",
|
||||||
"ntlm": "^0.1.3",
|
"ntlm": "^0.1.3",
|
||||||
"nwmatcher": "^1.4.4",
|
"nwmatcher": "^1.4.4",
|
||||||
"otp": "0.1.3",
|
"otpauth": "9.3.6",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"popper.js": "^1.16.1",
|
"popper.js": "^1.16.1",
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"protobufjs": "^7.2.2",
|
"protobufjs": "^7.3.1",
|
||||||
"qr-image": "^3.2.0",
|
"qr-image": "^3.2.0",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.2.2",
|
||||||
|
"rison": "^0.1.1",
|
||||||
"scryptsy": "^2.1.0",
|
"scryptsy": "^2.1.0",
|
||||||
"snackbarjs": "^1.1.0",
|
"snackbarjs": "^1.1.0",
|
||||||
"sortablejs": "^1.15.0",
|
"sortablejs": "^1.15.2",
|
||||||
"split.js": "^1.6.5",
|
"split.js": "^1.6.5",
|
||||||
"ssdeep.js": "0.0.3",
|
"ssdeep.js": "0.0.3",
|
||||||
"stream-browserify": "^3.0.0",
|
"stream-browserify": "^3.0.0",
|
||||||
"tesseract.js": "3.0.3",
|
"tesseract.js": "5.1.0",
|
||||||
"ua-parser-js": "^1.0.34",
|
"ua-parser-js": "^1.0.38",
|
||||||
"unorm": "^1.6.0",
|
"unorm": "^1.6.0",
|
||||||
"utf8": "^3.0.0",
|
"utf8": "^3.0.0",
|
||||||
"vkbeautify": "^0.99.3",
|
"vkbeautify": "^0.99.3",
|
||||||
"xmldom": "^0.6.0",
|
"xpath": "0.0.34",
|
||||||
"xpath": "0.0.32",
|
|
||||||
"xregexp": "^5.1.1",
|
"xregexp": "^5.1.1",
|
||||||
"zlibjs": "^0.3.1"
|
"zlibjs": "^0.3.1"
|
||||||
},
|
},
|
||||||
|
@ -188,7 +195,8 @@
|
||||||
"testui": "npx grunt testui",
|
"testui": "npx grunt testui",
|
||||||
"testuidev": "npx nightwatch --env=dev",
|
"testuidev": "npx nightwatch --env=dev",
|
||||||
"lint": "npx grunt lint",
|
"lint": "npx grunt lint",
|
||||||
"postinstall": "npx grunt exec:fixCryptoApiImports && npx grunt exec:fixSnackbarMarkup",
|
"lint:grammar": "cspell ./src",
|
||||||
|
"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",
|
"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",
|
"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`)'",
|
"getheapsize": "node -e 'console.log(`node heap limit = ${require(\"v8\").getHeapStatistics().heap_size_limit / (1024 * 1024)} Mb`)'",
|
||||||
|
|
|
@ -892,6 +892,23 @@ class Utils {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a string to its title case equivalent.
|
||||||
|
*
|
||||||
|
* @param {string} str
|
||||||
|
* @returns string
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // return "A Tiny String"
|
||||||
|
* Utils.toTitleCase("a tIny String");
|
||||||
|
*/
|
||||||
|
static toTitleCase(str) {
|
||||||
|
return str.replace(/\w\S*/g, function(txt) {
|
||||||
|
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes a URI fragment (#) or query (?) using a minimal amount of percent-encoding.
|
* Encodes a URI fragment (#) or query (?) using a minimal amount of percent-encoding.
|
||||||
*
|
*
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
"From Charcode",
|
"From Charcode",
|
||||||
"To Decimal",
|
"To Decimal",
|
||||||
"From Decimal",
|
"From Decimal",
|
||||||
|
"To Float",
|
||||||
|
"From Float",
|
||||||
"To Binary",
|
"To Binary",
|
||||||
"From Binary",
|
"From Binary",
|
||||||
"To Octal",
|
"To Octal",
|
||||||
|
@ -29,6 +31,8 @@
|
||||||
"To Base64",
|
"To Base64",
|
||||||
"From Base64",
|
"From Base64",
|
||||||
"Show Base64 offsets",
|
"Show Base64 offsets",
|
||||||
|
"To Base92",
|
||||||
|
"From Base92",
|
||||||
"To Base85",
|
"To Base85",
|
||||||
"From Base85",
|
"From Base85",
|
||||||
"To Base",
|
"To Base",
|
||||||
|
@ -67,7 +71,13 @@
|
||||||
"JSON to CSV",
|
"JSON to CSV",
|
||||||
"Avro to JSON",
|
"Avro to JSON",
|
||||||
"CBOR Encode",
|
"CBOR Encode",
|
||||||
"CBOR Decode"
|
"CBOR Decode",
|
||||||
|
"Caret/M-decode",
|
||||||
|
"Rison Encode",
|
||||||
|
"Rison Decode",
|
||||||
|
"To Modhex",
|
||||||
|
"From Modhex",
|
||||||
|
"MIME Decoding"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -81,6 +91,8 @@
|
||||||
"DES Decrypt",
|
"DES Decrypt",
|
||||||
"Triple DES Encrypt",
|
"Triple DES Encrypt",
|
||||||
"Triple DES Decrypt",
|
"Triple DES Decrypt",
|
||||||
|
"Fernet Encrypt",
|
||||||
|
"Fernet Decrypt",
|
||||||
"LS47 Encrypt",
|
"LS47 Encrypt",
|
||||||
"LS47 Decrypt",
|
"LS47 Decrypt",
|
||||||
"RC2 Encrypt",
|
"RC2 Encrypt",
|
||||||
|
@ -88,6 +100,8 @@
|
||||||
"RC4",
|
"RC4",
|
||||||
"RC4 Drop",
|
"RC4 Drop",
|
||||||
"ChaCha",
|
"ChaCha",
|
||||||
|
"Salsa20",
|
||||||
|
"XSalsa20",
|
||||||
"Rabbit",
|
"Rabbit",
|
||||||
"SM4 Encrypt",
|
"SM4 Encrypt",
|
||||||
"SM4 Decrypt",
|
"SM4 Decrypt",
|
||||||
|
@ -106,6 +120,8 @@
|
||||||
"XOR Brute Force",
|
"XOR Brute Force",
|
||||||
"Vigenère Encode",
|
"Vigenère Encode",
|
||||||
"Vigenère Decode",
|
"Vigenère Decode",
|
||||||
|
"XXTEA Encrypt",
|
||||||
|
"XXTEA Decrypt",
|
||||||
"To Morse Code",
|
"To Morse Code",
|
||||||
"From Morse Code",
|
"From Morse Code",
|
||||||
"Bacon Cipher Encode",
|
"Bacon Cipher Encode",
|
||||||
|
@ -151,11 +167,14 @@
|
||||||
"name": "Public Key",
|
"name": "Public Key",
|
||||||
"ops": [
|
"ops": [
|
||||||
"Parse X.509 certificate",
|
"Parse X.509 certificate",
|
||||||
|
"Parse X.509 CRL",
|
||||||
"Parse ASN.1 hex string",
|
"Parse ASN.1 hex string",
|
||||||
"PEM to Hex",
|
"PEM to Hex",
|
||||||
"Hex to PEM",
|
"Hex to PEM",
|
||||||
"Hex to Object Identifier",
|
"Hex to Object Identifier",
|
||||||
"Object Identifier to Hex",
|
"Object Identifier to Hex",
|
||||||
|
"PEM to JWK",
|
||||||
|
"JWK to PEM",
|
||||||
"Generate PGP Key Pair",
|
"Generate PGP Key Pair",
|
||||||
"PGP Encrypt",
|
"PGP Encrypt",
|
||||||
"PGP Decrypt",
|
"PGP Decrypt",
|
||||||
|
@ -167,7 +186,14 @@
|
||||||
"RSA Verify",
|
"RSA Verify",
|
||||||
"RSA Encrypt",
|
"RSA Encrypt",
|
||||||
"RSA Decrypt",
|
"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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -211,9 +237,14 @@
|
||||||
"Parse User Agent",
|
"Parse User Agent",
|
||||||
"Parse IP range",
|
"Parse IP range",
|
||||||
"Parse IPv6 address",
|
"Parse IPv6 address",
|
||||||
|
"IPv6 Transition Addresses",
|
||||||
"Parse IPv4 header",
|
"Parse IPv4 header",
|
||||||
|
"Strip IPv4 header",
|
||||||
"Parse TCP",
|
"Parse TCP",
|
||||||
|
"Strip TCP header",
|
||||||
|
"Parse TLS record",
|
||||||
"Parse UDP",
|
"Parse UDP",
|
||||||
|
"Strip UDP header",
|
||||||
"Parse SSH Host Key",
|
"Parse SSH Host Key",
|
||||||
"Parse URI",
|
"Parse URI",
|
||||||
"URL Encode",
|
"URL Encode",
|
||||||
|
@ -224,6 +255,8 @@
|
||||||
"VarInt Decode",
|
"VarInt Decode",
|
||||||
"JA3 Fingerprint",
|
"JA3 Fingerprint",
|
||||||
"JA3S Fingerprint",
|
"JA3S Fingerprint",
|
||||||
|
"JA4 Fingerprint",
|
||||||
|
"JA4Server Fingerprint",
|
||||||
"HASSH Client Fingerprint",
|
"HASSH Client Fingerprint",
|
||||||
"HASSH Server Fingerprint",
|
"HASSH Server Fingerprint",
|
||||||
"Format MAC addresses",
|
"Format MAC addresses",
|
||||||
|
@ -232,6 +265,7 @@
|
||||||
"Encode NetBIOS Name",
|
"Encode NetBIOS Name",
|
||||||
"Decode NetBIOS Name",
|
"Decode NetBIOS Name",
|
||||||
"Defang URL",
|
"Defang URL",
|
||||||
|
"Fang URL",
|
||||||
"Defang IP Addresses"
|
"Defang IP Addresses"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -243,7 +277,8 @@
|
||||||
"Unicode Text Format",
|
"Unicode Text Format",
|
||||||
"Remove Diacritics",
|
"Remove Diacritics",
|
||||||
"Unescape Unicode Characters",
|
"Unescape Unicode Characters",
|
||||||
"Convert to NATO alphabet"
|
"Convert to NATO alphabet",
|
||||||
|
"Convert Leet Speak"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -294,7 +329,10 @@
|
||||||
"Escape string",
|
"Escape string",
|
||||||
"Unescape string",
|
"Unescape string",
|
||||||
"Pseudo-Random Number Generator",
|
"Pseudo-Random Number Generator",
|
||||||
"Sleep"
|
"Sleep",
|
||||||
|
"File Tree",
|
||||||
|
"Take nth bytes",
|
||||||
|
"Drop nth bytes"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -306,6 +344,7 @@
|
||||||
"To UNIX Timestamp",
|
"To UNIX Timestamp",
|
||||||
"Windows Filetime to UNIX Timestamp",
|
"Windows Filetime to UNIX Timestamp",
|
||||||
"UNIX Timestamp to Windows Filetime",
|
"UNIX Timestamp to Windows Filetime",
|
||||||
|
"DateTime Delta",
|
||||||
"Extract dates",
|
"Extract dates",
|
||||||
"Get Time",
|
"Get Time",
|
||||||
"Sleep"
|
"Sleep"
|
||||||
|
@ -322,13 +361,15 @@
|
||||||
"Extract domains",
|
"Extract domains",
|
||||||
"Extract file paths",
|
"Extract file paths",
|
||||||
"Extract dates",
|
"Extract dates",
|
||||||
|
"Extract hashes",
|
||||||
"Regular expression",
|
"Regular expression",
|
||||||
"XPath expression",
|
"XPath expression",
|
||||||
"JPath expression",
|
"JPath expression",
|
||||||
"CSS selector",
|
"CSS selector",
|
||||||
"Extract EXIF",
|
"Extract EXIF",
|
||||||
"Extract ID3",
|
"Extract ID3",
|
||||||
"Extract Files"
|
"Extract Files",
|
||||||
|
"RAKE"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -351,7 +392,8 @@
|
||||||
"LZMA Decompress",
|
"LZMA Decompress",
|
||||||
"LZMA Compress",
|
"LZMA Compress",
|
||||||
"LZ4 Decompress",
|
"LZ4 Decompress",
|
||||||
"LZ4 Compress"
|
"LZ4 Compress",
|
||||||
|
"LZNT1 Decompress"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -392,6 +434,7 @@
|
||||||
"Scrypt",
|
"Scrypt",
|
||||||
"NT Hash",
|
"NT Hash",
|
||||||
"LM Hash",
|
"LM Hash",
|
||||||
|
"MurmurHash3",
|
||||||
"Fletcher-8 Checksum",
|
"Fletcher-8 Checksum",
|
||||||
"Fletcher-16 Checksum",
|
"Fletcher-16 Checksum",
|
||||||
"Fletcher-32 Checksum",
|
"Fletcher-32 Checksum",
|
||||||
|
|
|
@ -147,7 +147,7 @@ class ${moduleName} extends Operation {
|
||||||
this.name = "${result.opName}";
|
this.name = "${result.opName}";
|
||||||
this.module = "${result.module}";
|
this.module = "${result.module}";
|
||||||
this.description = "${(new EscapeString).run(result.description, ["Special chars", "Double"])}";
|
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.inputType = "${result.inputType}";
|
||||||
this.outputType = "${result.outputType}";
|
this.outputType = "${result.outputType}";
|
||||||
this.args = [
|
this.args = [
|
||||||
|
|
44
src/core/lib/Base92.mjs
Normal file
44
src/core/lib/Base92.mjs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Base92 resources.
|
||||||
|
*
|
||||||
|
* @author sg5506844 [sg5506844@gmail.com]
|
||||||
|
* @copyright Crown Copyright 2021
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base92 alphabet char
|
||||||
|
*
|
||||||
|
* @param {number} val
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
export function base92Chr(val) {
|
||||||
|
if (val < 0 || val >= 91) {
|
||||||
|
throw new OperationError("Invalid value");
|
||||||
|
}
|
||||||
|
if (val === 0)
|
||||||
|
return "!".charCodeAt(0);
|
||||||
|
else if (val <= 61)
|
||||||
|
return "#".charCodeAt(0) + val - 1;
|
||||||
|
else
|
||||||
|
return "a".charCodeAt(0) + val - 62;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base92 alphabet ord
|
||||||
|
*
|
||||||
|
* @param {string} val
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
export function base92Ord(val) {
|
||||||
|
if (val === "!")
|
||||||
|
return 0;
|
||||||
|
else if ("#" <= val && val <= "_")
|
||||||
|
return val.charCodeAt(0) - "#".charCodeAt(0) + 1;
|
||||||
|
else if ("a" <= val && val <= "}")
|
||||||
|
return val.charCodeAt(0) - "a".charCodeAt(0) + 62;
|
||||||
|
throw new OperationError(`${val} is not a base92 character`);
|
||||||
|
}
|
||||||
|
|
|
@ -224,8 +224,85 @@ export function chrEncWidth(page) {
|
||||||
* @copyright Crown Copyright 2019
|
* @copyright Crown Copyright 2019
|
||||||
* @license Apache-2.0
|
* @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
|
* @license Apache-2.0
|
||||||
*/
|
*/
|
||||||
export function encode(tempIVP, key, rounds, input) {
|
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);
|
const state = new Array(256).fill(0);
|
||||||
let j = 0, i = 0;
|
let j = 0, i = 0;
|
||||||
const result = [];
|
const result = [];
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
*
|
*
|
||||||
* @author Matt C [matt@artemisbot.uk]
|
* @author Matt C [matt@artemisbot.uk]
|
||||||
* @author n1474335 [n1474335@gmail.com]
|
* @author n1474335 [n1474335@gmail.com]
|
||||||
|
* @author Evie H [evie@evie.sh]
|
||||||
*
|
*
|
||||||
* @copyright Crown Copyright 2018
|
* @copyright Crown Copyright 2018
|
||||||
* @license Apache-2.0
|
* @license Apache-2.0
|
||||||
|
@ -10,6 +11,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import OperationError from "../errors/OperationError.mjs";
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
import CryptoJS from "crypto-js";
|
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.");
|
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++) {
|
for (let i = 0; i < input.length; i++) {
|
||||||
if (alphabet.indexOf(input[i]) >= 0) {
|
if (alphabet.indexOf(input[i]) >= 0) {
|
||||||
// Uses the affine function ax+b % m = y (where m is length of the alphabet)
|
// Uses the affine function ax+b % m = y (where m is length of the alphabet)
|
||||||
|
|
|
@ -62,3 +62,9 @@ export const URL_REGEX = new RegExp(protocol + hostname + "(?:" + port + ")?(?:"
|
||||||
* Domain name regular expression
|
* Domain name regular expression
|
||||||
*/
|
*/
|
||||||
export const DOMAIN_REGEX = /\b((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b/ig;
|
export const DOMAIN_REGEX = /\b((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b/ig;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DMARC Domain name regular expression
|
||||||
|
*/
|
||||||
|
export const DMARC_DOMAIN_REGEX = /\b((?=[a-z0-9_-]{1,63}\.)(xn--)?[a-z0-9_]+(-[a-z0-9_]+)*\.)+[a-z]{2,63}\b/ig;
|
||||||
|
|
|
@ -72,6 +72,27 @@ export const FILE_SIGNATURES = {
|
||||||
},
|
},
|
||||||
extractor: extractWEBP
|
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",
|
name: "Camera Image File Format",
|
||||||
extension: "crw",
|
extension: "crw",
|
||||||
|
@ -2727,7 +2748,7 @@ export function extractGIF(bytes, offset) {
|
||||||
stream.moveForwardsBy(11);
|
stream.moveForwardsBy(11);
|
||||||
|
|
||||||
// Loop until next Graphic Control Extension.
|
// 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.moveBackwardsBy(2);
|
||||||
stream.moveForwardsBy(stream.readInt(1));
|
stream.moveForwardsBy(stream.readInt(1));
|
||||||
if (!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
|
||||||
|
}
|
||||||
|
}
|
88
src/core/lib/LZNT1.mjs
Normal file
88
src/core/lib/LZNT1.mjs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* LZNT1 Decompress.
|
||||||
|
*
|
||||||
|
* @author 0xThiebaut [thiebaut.dev]
|
||||||
|
* @copyright Crown Copyright 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*
|
||||||
|
* https://github.com/Velocidex/go-ntfs/blob/master/parser%2Flznt1.go
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
|
||||||
|
const COMPRESSED_MASK = 1 << 15,
|
||||||
|
SIZE_MASK = (1 << 12) - 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} offset
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
function getDisplacement(offset) {
|
||||||
|
let result = 0;
|
||||||
|
while (offset >= 0x10) {
|
||||||
|
offset >>= 1;
|
||||||
|
result += 1;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {byteArray} compressed
|
||||||
|
* @returns {byteArray}
|
||||||
|
*/
|
||||||
|
export function decompress(compressed) {
|
||||||
|
const decompressed = Array();
|
||||||
|
let coffset = 0;
|
||||||
|
|
||||||
|
while (coffset + 2 <= compressed.length) {
|
||||||
|
const doffset = decompressed.length;
|
||||||
|
|
||||||
|
const blockHeader = Utils.byteArrayToInt(compressed.slice(coffset, coffset + 2), "little");
|
||||||
|
coffset += 2;
|
||||||
|
|
||||||
|
const size = blockHeader & SIZE_MASK;
|
||||||
|
const blockEnd = coffset + size + 1;
|
||||||
|
|
||||||
|
if (size === 0) {
|
||||||
|
break;
|
||||||
|
} else if (compressed.length < coffset + size) {
|
||||||
|
throw new OperationError("Malformed LZNT1 stream: Block too small! Has the stream been truncated?");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((blockHeader & COMPRESSED_MASK) !== 0) {
|
||||||
|
while (coffset < blockEnd) {
|
||||||
|
let header = compressed[coffset++];
|
||||||
|
|
||||||
|
for (let i = 0; i < 8 && coffset < blockEnd; i++) {
|
||||||
|
if ((header & 1) === 0) {
|
||||||
|
decompressed.push(compressed[coffset++]);
|
||||||
|
} else {
|
||||||
|
const pointer = Utils.byteArrayToInt(compressed.slice(coffset, coffset + 2), "little");
|
||||||
|
coffset += 2;
|
||||||
|
|
||||||
|
const displacement = getDisplacement(decompressed.length - doffset - 1);
|
||||||
|
const symbolOffset = (pointer >> (12 - displacement)) + 1;
|
||||||
|
const symbolLength = (pointer & (0xFFF >> displacement)) + 2;
|
||||||
|
const shiftOffset = decompressed.length - symbolOffset;
|
||||||
|
|
||||||
|
for (let shiftDelta = 0; shiftDelta < symbolLength + 1; shiftDelta++) {
|
||||||
|
const shift = shiftOffset + shiftDelta;
|
||||||
|
if (shift < 0 || decompressed.length <= shift) {
|
||||||
|
throw new OperationError("Malformed LZNT1 stream: Invalid shift!");
|
||||||
|
}
|
||||||
|
decompressed.push(decompressed[shift]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
header >>= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
decompressed.push(...compressed.slice(coffset, coffset + size + 1));
|
||||||
|
coffset += size + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return decompressed;
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ import Utils, { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import Recipe from "../Recipe.mjs";
|
import Recipe from "../Recipe.mjs";
|
||||||
import Dish from "../Dish.mjs";
|
import Dish from "../Dish.mjs";
|
||||||
import {detectFileType, isType} from "./FileType.mjs";
|
import {detectFileType, isType} from "./FileType.mjs";
|
||||||
|
import {isUTF8} from "./ChrEnc.mjs";
|
||||||
import chiSquared from "chi-squared";
|
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.
|
* Calculates the Shannon entropy of the input data.
|
||||||
*
|
*
|
||||||
|
@ -336,7 +261,7 @@ class Magic {
|
||||||
data: this.inputStr.slice(0, 100),
|
data: this.inputStr.slice(0, 100),
|
||||||
languageScores: this.detectLanguage(extLang),
|
languageScores: this.detectLanguage(extLang),
|
||||||
fileType: this.detectFileType(),
|
fileType: this.detectFileType(),
|
||||||
isUTF8: this.isUTF8(),
|
isUTF8: !!isUTF8(this.inputBuffer),
|
||||||
entropy: this.calcEntropy(),
|
entropy: this.calcEntropy(),
|
||||||
matchingOps: matchingOps,
|
matchingOps: matchingOps,
|
||||||
useful: useful,
|
useful: useful,
|
||||||
|
|
165
src/core/lib/Modhex.mjs
Normal file
165
src/core/lib/Modhex.mjs
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
/**
|
||||||
|
* @author linuxgemini [ilteris@asenkron.com.tr]
|
||||||
|
* @copyright Crown Copyright 2024
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import { fromHex, toHex } from "./Hex.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modhex alphabet.
|
||||||
|
*/
|
||||||
|
const MODHEX_ALPHABET = "cbdefghijklnrtuv";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modhex alphabet map.
|
||||||
|
*/
|
||||||
|
const MODHEX_ALPHABET_MAP = MODHEX_ALPHABET.split("");
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hex alphabet to substitute Modhex.
|
||||||
|
*/
|
||||||
|
const HEX_ALPHABET = "0123456789abcdef";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hex alphabet map to substitute Modhex.
|
||||||
|
*/
|
||||||
|
const HEX_ALPHABET_MAP = HEX_ALPHABET.split("");
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a byte array into a modhex string.
|
||||||
|
*
|
||||||
|
* @param {byteArray|Uint8Array|ArrayBuffer} data
|
||||||
|
* @param {string} [delim=" "]
|
||||||
|
* @param {number} [padding=2]
|
||||||
|
* @returns {string}
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // returns "cl bf bu"
|
||||||
|
* toModhex([10,20,30]);
|
||||||
|
*
|
||||||
|
* // returns "cl:bf:bu"
|
||||||
|
* toModhex([10,20,30], ":");
|
||||||
|
*/
|
||||||
|
export function toModhex(data, delim=" ", padding=2, extraDelim="", lineSize=0) {
|
||||||
|
if (!data) return "";
|
||||||
|
if (data instanceof ArrayBuffer) data = new Uint8Array(data);
|
||||||
|
|
||||||
|
const regularHexString = toHex(data, "", padding, "", 0);
|
||||||
|
|
||||||
|
let modhexString = "";
|
||||||
|
for (const letter of regularHexString.split("")) {
|
||||||
|
modhexString += MODHEX_ALPHABET_MAP[HEX_ALPHABET_MAP.indexOf(letter)];
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = "";
|
||||||
|
const groupingRegexp = new RegExp(`.{1,${padding}}`, "g");
|
||||||
|
const groupedModhex = modhexString.match(groupingRegexp);
|
||||||
|
|
||||||
|
for (let i = 0; i < groupedModhex.length; i++) {
|
||||||
|
const group = groupedModhex[i];
|
||||||
|
output += group + delim;
|
||||||
|
|
||||||
|
if (extraDelim) {
|
||||||
|
output += extraDelim;
|
||||||
|
}
|
||||||
|
// Add LF after each lineSize amount of bytes but not at the end
|
||||||
|
if ((i !== groupedModhex.length - 1) && ((i + 1) % lineSize === 0)) {
|
||||||
|
output += "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the extraDelim at the end (if there is one)
|
||||||
|
// and remove the delim at the end, but if it's prepended there's nothing to remove
|
||||||
|
const rTruncLen = extraDelim.length + delim.length;
|
||||||
|
if (rTruncLen) {
|
||||||
|
// If rTruncLen === 0 then output.slice(0,0) will be returned, which is nothing
|
||||||
|
return output.slice(0, -rTruncLen);
|
||||||
|
} else {
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a byte array into a modhex string as efficiently as possible with no options.
|
||||||
|
*
|
||||||
|
* @param {byteArray|Uint8Array|ArrayBuffer} data
|
||||||
|
* @returns {string}
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // returns "clbfbu"
|
||||||
|
* toModhexFast([10,20,30]);
|
||||||
|
*/
|
||||||
|
export function toModhexFast(data) {
|
||||||
|
if (!data) return "";
|
||||||
|
if (data instanceof ArrayBuffer) data = new Uint8Array(data);
|
||||||
|
|
||||||
|
const output = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
output.push(MODHEX_ALPHABET_MAP[(data[i] >> 4) & 0xf]);
|
||||||
|
output.push(MODHEX_ALPHABET_MAP[data[i] & 0xf]);
|
||||||
|
}
|
||||||
|
return output.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a modhex string into a byte array.
|
||||||
|
*
|
||||||
|
* @param {string} data
|
||||||
|
* @param {string} [delim]
|
||||||
|
* @param {number} [byteLen=2]
|
||||||
|
* @returns {byteArray}
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // returns [10,20,30]
|
||||||
|
* fromModhex("cl bf bu");
|
||||||
|
*
|
||||||
|
* // returns [10,20,30]
|
||||||
|
* fromModhex("cl:bf:bu", "Colon");
|
||||||
|
*/
|
||||||
|
export function fromModhex(data, delim="Auto", byteLen=2) {
|
||||||
|
if (byteLen < 1 || Math.round(byteLen) !== byteLen)
|
||||||
|
throw new OperationError("Byte length must be a positive integer");
|
||||||
|
|
||||||
|
// The `.replace(/\s/g, "")` an interesting workaround: Hex "multiline" tests aren't actually
|
||||||
|
// multiline. Tests for Modhex fixes that, thus exposing the issue.
|
||||||
|
data = data.toLowerCase().replace(/\s/g, "");
|
||||||
|
|
||||||
|
if (delim !== "None") {
|
||||||
|
const delimRegex = delim === "Auto" ? /[^cbdefghijklnrtuv]/gi : Utils.regexRep(delim);
|
||||||
|
data = data.split(delimRegex);
|
||||||
|
} else {
|
||||||
|
data = [data];
|
||||||
|
}
|
||||||
|
|
||||||
|
let regularHexString = "";
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
for (const letter of data[i].split("")) {
|
||||||
|
regularHexString += HEX_ALPHABET_MAP[MODHEX_ALPHABET_MAP.indexOf(letter)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const output = fromHex(regularHexString, "None", byteLen);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To Modhex delimiters.
|
||||||
|
*/
|
||||||
|
export const TO_MODHEX_DELIM_OPTIONS = ["Space", "Percent", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "None"];
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From Modhex delimiters.
|
||||||
|
*/
|
||||||
|
export const FROM_MODHEX_DELIM_OPTIONS = ["Auto"].concat(TO_MODHEX_DELIM_OPTIONS);
|
|
@ -26,6 +26,9 @@ export function objToTable(obj, nested=false) {
|
||||||
</tr>`;
|
</tr>`;
|
||||||
|
|
||||||
for (const key in obj) {
|
for (const key in obj) {
|
||||||
|
if (typeof obj[key] === "function")
|
||||||
|
continue;
|
||||||
|
|
||||||
html += `<tr><td style='word-wrap: break-word'>${key}</td>`;
|
html += `<tr><td style='word-wrap: break-word'>${key}</td>`;
|
||||||
if (typeof obj[key] === "object")
|
if (typeof obj[key] === "object")
|
||||||
html += `<td style='padding: 0'>${objToTable(obj[key], true)}</td>`;
|
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 jsQR from "jsqr";
|
||||||
import qr from "qr-image";
|
import qr from "qr-image";
|
||||||
import Utils from "../Utils.mjs";
|
import Utils from "../Utils.mjs";
|
||||||
import jimp from "jimp";
|
import Jimp from "jimp/es/index.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a QR code image from an image
|
* Parses a QR code image from an image
|
||||||
|
@ -22,7 +22,7 @@ import jimp from "jimp";
|
||||||
export async function parseQrCode(input, normalise) {
|
export async function parseQrCode(input, normalise) {
|
||||||
let image;
|
let image;
|
||||||
try {
|
try {
|
||||||
image = await jimp.read(input);
|
image = await Jimp.read(input);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error opening image. (${err})`);
|
throw new OperationError(`Error opening image. (${err})`);
|
||||||
}
|
}
|
||||||
|
@ -33,8 +33,8 @@ export async function parseQrCode(input, normalise) {
|
||||||
image.background(0xFFFFFFFF);
|
image.background(0xFFFFFFFF);
|
||||||
image.normalize();
|
image.normalize();
|
||||||
image.greyscale();
|
image.greyscale();
|
||||||
image = await image.getBufferAsync(jimp.MIME_JPEG);
|
image = await image.getBufferAsync(Jimp.MIME_JPEG);
|
||||||
image = await jimp.read(image);
|
image = await Jimp.read(image);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error normalising image. (${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.
|
* Stream constructor.
|
||||||
*
|
*
|
||||||
* @param {Uint8Array} input
|
* @param {Uint8Array} input
|
||||||
|
* @param {number} pos
|
||||||
|
* @param {number} bitPos
|
||||||
*/
|
*/
|
||||||
constructor(input) {
|
constructor(input, pos=0, bitPos=0) {
|
||||||
this.bytes = input;
|
this.bytes = input;
|
||||||
this.length = this.bytes.length;
|
this.length = this.bytes.length;
|
||||||
this.position = 0;
|
this.position = pos;
|
||||||
this.bitPos = 0;
|
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);
|
||||||
|
}
|
|
@ -22,7 +22,13 @@ class AddLineNumbers extends Operation {
|
||||||
this.description = "Adds line numbers to the output.";
|
this.description = "Adds line numbers to the output.";
|
||||||
this.inputType = "string";
|
this.inputType = "string";
|
||||||
this.outputType = "string";
|
this.outputType = "string";
|
||||||
this.args = [];
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Offset",
|
||||||
|
"type": "number",
|
||||||
|
"value": 0
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,10 +39,11 @@ class AddLineNumbers extends Operation {
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const lines = input.split("\n"),
|
const lines = input.split("\n"),
|
||||||
width = lines.length.toString().length;
|
width = lines.length.toString().length;
|
||||||
|
const offset = args[0] ? parseInt(args[0], 10) : 0;
|
||||||
let output = "";
|
let output = "";
|
||||||
|
|
||||||
for (let n = 0; n < lines.length; n++) {
|
for (let n = 0; n < lines.length; n++) {
|
||||||
output += (n+1).toString().padStart(width, " ") + " " + lines[n] + "\n";
|
output += (n+1+offset).toString().padStart(width, " ") + " " + lines[n] + "\n";
|
||||||
}
|
}
|
||||||
return output.slice(0, output.length-1);
|
return output.slice(0, output.length-1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import jimp from "jimp";
|
import Jimp from "jimp/es/index.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add Text To Image operation
|
* Add Text To Image operation
|
||||||
|
@ -127,7 +127,7 @@ class AddTextToImage extends Operation {
|
||||||
|
|
||||||
let image;
|
let image;
|
||||||
try {
|
try {
|
||||||
image = await jimp.read(input);
|
image = await Jimp.read(input);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error loading image. (${err})`);
|
throw new OperationError(`Error loading image. (${err})`);
|
||||||
}
|
}
|
||||||
|
@ -163,7 +163,7 @@ class AddTextToImage extends Operation {
|
||||||
const font = fontsMap[fontFace];
|
const font = fontsMap[fontFace];
|
||||||
|
|
||||||
// LoadFont needs an absolute url, so append the font name to self.docURL
|
// 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) {
|
jimpFont.pages.forEach(function(page) {
|
||||||
if (page.bitmap) {
|
if (page.bitmap) {
|
||||||
|
@ -190,7 +190,7 @@ class AddTextToImage extends Operation {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create a temporary image to hold the rendered text
|
// 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);
|
textImage.print(jimpFont, 0, 0, text);
|
||||||
|
|
||||||
// Scale the rendered text image to the correct size
|
// Scale the rendered text image to the correct size
|
||||||
|
@ -198,9 +198,9 @@ class AddTextToImage extends Operation {
|
||||||
if (size !== 1) {
|
if (size !== 1) {
|
||||||
// Use bicubic for decreasing size
|
// Use bicubic for decreasing size
|
||||||
if (size > 1) {
|
if (size > 1) {
|
||||||
textImage.scale(scaleFactor, jimp.RESIZE_BICUBIC);
|
textImage.scale(scaleFactor, Jimp.RESIZE_BICUBIC);
|
||||||
} else {
|
} else {
|
||||||
textImage.scale(scaleFactor, jimp.RESIZE_BILINEAR);
|
textImage.scale(scaleFactor, Jimp.RESIZE_BILINEAR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,9 +234,9 @@ class AddTextToImage extends Operation {
|
||||||
|
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.getMIME() === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -70,10 +70,14 @@ class BlowfishDecrypt extends Operation {
|
||||||
inputType = args[3],
|
inputType = args[3],
|
||||||
outputType = args[4];
|
outputType = args[4];
|
||||||
|
|
||||||
if (key.length !== 8) {
|
if (key.length < 4 || key.length > 56) {
|
||||||
throw new OperationError(`Invalid key length: ${key.length} bytes
|
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 (mode !== "ECB" && iv.length !== 8) {
|
||||||
|
throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
input = Utils.convertToByteString(input, inputType);
|
input = Utils.convertToByteString(input, inputType);
|
||||||
|
|
|
@ -70,10 +70,14 @@ class BlowfishEncrypt extends Operation {
|
||||||
inputType = args[3],
|
inputType = args[3],
|
||||||
outputType = args[4];
|
outputType = args[4];
|
||||||
|
|
||||||
if (key.length !== 8) {
|
if (key.length < 4 || key.length > 56) {
|
||||||
throw new OperationError(`Invalid key length: ${key.length} bytes
|
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 (mode !== "ECB" && iv.length !== 8) {
|
||||||
|
throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
input = Utils.convertToByteString(input, inputType);
|
input = Utils.convertToByteString(input, inputType);
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { gaussianBlur } from "../lib/ImageManipulation.mjs";
|
import { gaussianBlur } from "../lib/ImageManipulation.mjs";
|
||||||
import jimp from "jimp";
|
import Jimp from "jimp/es/index.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blur Image operation
|
* Blur Image operation
|
||||||
|
@ -59,7 +59,7 @@ class BlurImage extends Operation {
|
||||||
|
|
||||||
let image;
|
let image;
|
||||||
try {
|
try {
|
||||||
image = await jimp.read(input);
|
image = await Jimp.read(input);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error loading image. (${err})`);
|
throw new OperationError(`Error loading image. (${err})`);
|
||||||
}
|
}
|
||||||
|
@ -79,9 +79,9 @@ class BlurImage extends Operation {
|
||||||
|
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.getMIME() === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
import Operation from "../Operation.mjs";
|
import Operation from "../Operation.mjs";
|
||||||
import OperationError from "../errors/OperationError.mjs";
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
import xmldom from "xmldom";
|
import xmldom from "@xmldom/xmldom";
|
||||||
import nwmatcher from "nwmatcher";
|
import nwmatcher from "nwmatcher";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -21,7 +21,7 @@ class CTPH extends Operation {
|
||||||
this.name = "CTPH";
|
this.name = "CTPH";
|
||||||
this.module = "Crypto";
|
this.module = "Crypto";
|
||||||
this.description = "Context Triggered Piecewise Hashing, also called Fuzzy Hashing, can match inputs that have homologies. Such inputs have sequences of identical bytes in the same order, although bytes in between these sequences may be different in both content and length.<br><br>CTPH was originally based on the work of Dr. Andrew Tridgell and a spam email detector called SpamSum. This method was adapted by Jesse Kornblum and published at the DFRWS conference in 2006 in a paper 'Identifying Almost Identical Files Using Context Triggered Piecewise Hashing'.";
|
this.description = "Context Triggered Piecewise Hashing, also called Fuzzy Hashing, can match inputs that have homologies. Such inputs have sequences of identical bytes in the same order, although bytes in between these sequences may be different in both content and length.<br><br>CTPH was originally based on the work of Dr. Andrew Tridgell and a spam email detector called SpamSum. This method was adapted by Jesse Kornblum and published at the DFRWS conference in 2006 in a paper 'Identifying Almost Identical Files Using Context Triggered Piecewise Hashing'.";
|
||||||
this.infoURL = "https://forensicswiki.xyz/wiki/index.php?title=Context_Triggered_Piecewise_Hashing";
|
this.infoURL = "https://forensics.wiki/context_triggered_piecewise_hashing/";
|
||||||
this.inputType = "string";
|
this.inputType = "string";
|
||||||
this.outputType = "string";
|
this.outputType = "string";
|
||||||
this.args = [];
|
this.args = [];
|
||||||
|
|
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();
|
super();
|
||||||
|
|
||||||
this.name = "ChaCha";
|
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.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.infoURL = "https://wikipedia.org/wiki/Salsa20#ChaCha_variant";
|
||||||
this.inputType = "string";
|
this.inputType = "string";
|
||||||
|
@ -191,7 +191,7 @@ ChaCha uses a nonce of 8 or 12 bytes (64 or 96 bits).`);
|
||||||
if (outputType === "Hex") {
|
if (outputType === "Hex") {
|
||||||
return toHex(output);
|
return toHex(output);
|
||||||
} else {
|
} else {
|
||||||
return Utils.arrayBufferToStr(output);
|
return Utils.arrayBufferToStr(Uint8Array.from(output).buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ class CompareCTPHHashes extends Operation {
|
||||||
this.name = "Compare CTPH hashes";
|
this.name = "Compare CTPH hashes";
|
||||||
this.module = "Crypto";
|
this.module = "Crypto";
|
||||||
this.description = "Compares two Context Triggered Piecewise Hashing (CTPH) fuzzy hashes to determine the similarity between them on a scale of 0 to 100.";
|
this.description = "Compares two Context Triggered Piecewise Hashing (CTPH) fuzzy hashes to determine the similarity between them on a scale of 0 to 100.";
|
||||||
this.infoURL = "https://forensicswiki.xyz/wiki/index.php?title=Context_Triggered_Piecewise_Hashing";
|
this.infoURL = "https://forensics.wiki/context_triggered_piecewise_hashing/";
|
||||||
this.inputType = "string";
|
this.inputType = "string";
|
||||||
this.outputType = "Number";
|
this.outputType = "Number";
|
||||||
this.args = [
|
this.args = [
|
||||||
|
|
|
@ -24,7 +24,7 @@ class CompareSSDEEPHashes extends Operation {
|
||||||
this.name = "Compare SSDEEP hashes";
|
this.name = "Compare SSDEEP hashes";
|
||||||
this.module = "Crypto";
|
this.module = "Crypto";
|
||||||
this.description = "Compares two SSDEEP fuzzy hashes to determine the similarity between them on a scale of 0 to 100.";
|
this.description = "Compares two SSDEEP fuzzy hashes to determine the similarity between them on a scale of 0 to 100.";
|
||||||
this.infoURL = "https://forensicswiki.xyz/wiki/index.php?title=Ssdeep";
|
this.infoURL = "https://forensics.wiki/ssdeep/";
|
||||||
this.inputType = "string";
|
this.inputType = "string";
|
||||||
this.outputType = "Number";
|
this.outputType = "Number";
|
||||||
this.args = [
|
this.args = [
|
||||||
|
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import jimp from "jimp";
|
import Jimp from "jimp/es/index.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contain Image operation
|
* Contain Image operation
|
||||||
|
@ -91,20 +91,20 @@ class ContainImage extends Operation {
|
||||||
const [width, height, hAlign, vAlign, alg, opaqueBg] = args;
|
const [width, height, hAlign, vAlign, alg, opaqueBg] = args;
|
||||||
|
|
||||||
const resizeMap = {
|
const resizeMap = {
|
||||||
"Nearest Neighbour": jimp.RESIZE_NEAREST_NEIGHBOR,
|
"Nearest Neighbour": Jimp.RESIZE_NEAREST_NEIGHBOR,
|
||||||
"Bilinear": jimp.RESIZE_BILINEAR,
|
"Bilinear": Jimp.RESIZE_BILINEAR,
|
||||||
"Bicubic": jimp.RESIZE_BICUBIC,
|
"Bicubic": Jimp.RESIZE_BICUBIC,
|
||||||
"Hermite": jimp.RESIZE_HERMITE,
|
"Hermite": Jimp.RESIZE_HERMITE,
|
||||||
"Bezier": jimp.RESIZE_BEZIER
|
"Bezier": Jimp.RESIZE_BEZIER
|
||||||
};
|
};
|
||||||
|
|
||||||
const alignMap = {
|
const alignMap = {
|
||||||
"Left": jimp.HORIZONTAL_ALIGN_LEFT,
|
"Left": Jimp.HORIZONTAL_ALIGN_LEFT,
|
||||||
"Center": jimp.HORIZONTAL_ALIGN_CENTER,
|
"Center": Jimp.HORIZONTAL_ALIGN_CENTER,
|
||||||
"Right": jimp.HORIZONTAL_ALIGN_RIGHT,
|
"Right": Jimp.HORIZONTAL_ALIGN_RIGHT,
|
||||||
"Top": jimp.VERTICAL_ALIGN_TOP,
|
"Top": Jimp.VERTICAL_ALIGN_TOP,
|
||||||
"Middle": jimp.VERTICAL_ALIGN_MIDDLE,
|
"Middle": Jimp.VERTICAL_ALIGN_MIDDLE,
|
||||||
"Bottom": jimp.VERTICAL_ALIGN_BOTTOM
|
"Bottom": Jimp.VERTICAL_ALIGN_BOTTOM
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isImage(input)) {
|
if (!isImage(input)) {
|
||||||
|
@ -113,7 +113,7 @@ class ContainImage extends Operation {
|
||||||
|
|
||||||
let image;
|
let image;
|
||||||
try {
|
try {
|
||||||
image = await jimp.read(input);
|
image = await Jimp.read(input);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error loading image. (${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]);
|
image.contain(width, height, alignMap[hAlign] | alignMap[vAlign], resizeMap[alg]);
|
||||||
|
|
||||||
if (opaqueBg) {
|
if (opaqueBg) {
|
||||||
const newImage = await jimp.read(width, height, 0x000000FF);
|
const newImage = await Jimp.read(width, height, 0x000000FF);
|
||||||
newImage.blit(image, 0, 0);
|
newImage.blit(image, 0, 0);
|
||||||
image = newImage;
|
image = newImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.getMIME() === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import Operation from "../Operation.mjs";
|
||||||
import OperationError from "../errors/OperationError.mjs";
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import jimp from "jimp";
|
import Jimp from "jimp/es/index.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert Image Format operation
|
* Convert Image Format operation
|
||||||
|
@ -76,19 +76,19 @@ class ConvertImageFormat extends Operation {
|
||||||
async run(input, args) {
|
async run(input, args) {
|
||||||
const [format, jpegQuality, pngFilterType, pngDeflateLevel] = args;
|
const [format, jpegQuality, pngFilterType, pngDeflateLevel] = args;
|
||||||
const formatMap = {
|
const formatMap = {
|
||||||
"JPEG": jimp.MIME_JPEG,
|
"JPEG": Jimp.MIME_JPEG,
|
||||||
"PNG": jimp.MIME_PNG,
|
"PNG": Jimp.MIME_PNG,
|
||||||
"BMP": jimp.MIME_BMP,
|
"BMP": Jimp.MIME_BMP,
|
||||||
"TIFF": jimp.MIME_TIFF
|
"TIFF": Jimp.MIME_TIFF
|
||||||
};
|
};
|
||||||
|
|
||||||
const pngFilterMap = {
|
const pngFilterMap = {
|
||||||
"Auto": jimp.PNG_FILTER_AUTO,
|
"Auto": Jimp.PNG_FILTER_AUTO,
|
||||||
"None": jimp.PNG_FILTER_NONE,
|
"None": Jimp.PNG_FILTER_NONE,
|
||||||
"Sub": jimp.PNG_FILTER_SUB,
|
"Sub": Jimp.PNG_FILTER_SUB,
|
||||||
"Up": jimp.PNG_FILTER_UP,
|
"Up": Jimp.PNG_FILTER_UP,
|
||||||
"Average": jimp.PNG_FILTER_AVERAGE,
|
"Average": Jimp.PNG_FILTER_AVERAGE,
|
||||||
"Paeth": jimp.PNG_FILTER_PATH
|
"Paeth": Jimp.PNG_FILTER_PATH
|
||||||
};
|
};
|
||||||
|
|
||||||
const mime = formatMap[format];
|
const mime = formatMap[format];
|
||||||
|
@ -98,7 +98,7 @@ class ConvertImageFormat extends Operation {
|
||||||
}
|
}
|
||||||
let image;
|
let image;
|
||||||
try {
|
try {
|
||||||
image = await jimp.read(input);
|
image = await Jimp.read(input);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error opening image file. (${err})`);
|
throw new OperationError(`Error opening image file. (${err})`);
|
||||||
}
|
}
|
||||||
|
|
113
src/core/operations/ConvertLeetSpeak.mjs
Normal file
113
src/core/operations/ConvertLeetSpeak.mjs
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
/**
|
||||||
|
* @author bartblaze []
|
||||||
|
* @copyright Crown Copyright 2025
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Leet Speak operation
|
||||||
|
*/
|
||||||
|
class ConvertLeetSpeak extends Operation {
|
||||||
|
/**
|
||||||
|
* ConvertLeetSpeak constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Convert Leet Speak";
|
||||||
|
this.module = "Default";
|
||||||
|
this.description = "Converts to and from Leet Speak";
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/Leet";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
name: "Direction",
|
||||||
|
type: "option",
|
||||||
|
value: ["To Leet Speak", "From Leet Speak"],
|
||||||
|
defaultIndex: 0
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const direction = args[0];
|
||||||
|
if (direction === "To Leet Speak") {
|
||||||
|
return input.replace(/[abcdefghijklmnopqrstuvwxyz]/gi, char => {
|
||||||
|
return toLeetMap[char.toLowerCase()] || char;
|
||||||
|
});
|
||||||
|
} else if (direction === "From Leet Speak") {
|
||||||
|
return input.replace(/[48cd3f6h1jklmn0pqr57uvwxyz]/g, char => {
|
||||||
|
return fromLeetMap[char] || char;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const toLeetMap = {
|
||||||
|
"a": "4",
|
||||||
|
"b": "b",
|
||||||
|
"c": "c",
|
||||||
|
"d": "d",
|
||||||
|
"e": "3",
|
||||||
|
"f": "f",
|
||||||
|
"g": "g",
|
||||||
|
"h": "h",
|
||||||
|
"i": "1",
|
||||||
|
"j": "j",
|
||||||
|
"k": "k",
|
||||||
|
"l": "l",
|
||||||
|
"m": "m",
|
||||||
|
"n": "n",
|
||||||
|
"o": "0",
|
||||||
|
"p": "p",
|
||||||
|
"q": "q",
|
||||||
|
"r": "r",
|
||||||
|
"s": "5",
|
||||||
|
"t": "7",
|
||||||
|
"u": "u",
|
||||||
|
"v": "v",
|
||||||
|
"w": "w",
|
||||||
|
"x": "x",
|
||||||
|
"y": "y",
|
||||||
|
"z": "z"
|
||||||
|
};
|
||||||
|
|
||||||
|
const fromLeetMap = {
|
||||||
|
"4": "a",
|
||||||
|
"b": "b",
|
||||||
|
"c": "c",
|
||||||
|
"d": "d",
|
||||||
|
"3": "e",
|
||||||
|
"f": "f",
|
||||||
|
"g": "g",
|
||||||
|
"h": "h",
|
||||||
|
"1": "i",
|
||||||
|
"j": "j",
|
||||||
|
"k": "k",
|
||||||
|
"l": "l",
|
||||||
|
"m": "m",
|
||||||
|
"n": "n",
|
||||||
|
"0": "o",
|
||||||
|
"p": "p",
|
||||||
|
"q": "q",
|
||||||
|
"r": "r",
|
||||||
|
"5": "s",
|
||||||
|
"7": "t",
|
||||||
|
"u": "u",
|
||||||
|
"v": "v",
|
||||||
|
"w": "w",
|
||||||
|
"x": "x",
|
||||||
|
"y": "y",
|
||||||
|
"z": "z"
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConvertLeetSpeak;
|
||||||
|
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import jimp from "jimp";
|
import jimp from "jimp/es/index.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cover Image operation
|
* Cover Image operation
|
||||||
|
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import jimp from "jimp";
|
import Jimp from "jimp/es/index.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Crop Image operation
|
* Crop Image operation
|
||||||
|
@ -99,7 +99,7 @@ class CropImage extends Operation {
|
||||||
|
|
||||||
let image;
|
let image;
|
||||||
try {
|
try {
|
||||||
image = await jimp.read(input);
|
image = await Jimp.read(input);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error loading image. (${err})`);
|
throw new OperationError(`Error loading image. (${err})`);
|
||||||
}
|
}
|
||||||
|
@ -119,9 +119,9 @@ class CropImage extends Operation {
|
||||||
|
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.getMIME() === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -22,7 +22,7 @@ class DESDecrypt extends Operation {
|
||||||
|
|
||||||
this.name = "DES Decrypt";
|
this.name = "DES Decrypt";
|
||||||
this.module = "Ciphers";
|
this.module = "Ciphers";
|
||||||
this.description = "DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.<br><br><b>Key:</b> DES uses a key length of 8 bytes (64 bits).<br>Triple DES uses a key length of 24 bytes (192 bits).<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used as a default.";
|
this.description = "DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.<br><br><b>Key:</b> DES uses a key length of 8 bytes (64 bits).<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used as a default.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Data_Encryption_Standard";
|
this.infoURL = "https://wikipedia.org/wiki/Data_Encryption_Standard";
|
||||||
this.inputType = "string";
|
this.inputType = "string";
|
||||||
this.outputType = "string";
|
this.outputType = "string";
|
||||||
|
@ -72,8 +72,7 @@ class DESDecrypt extends Operation {
|
||||||
if (key.length !== 8) {
|
if (key.length !== 8) {
|
||||||
throw new OperationError(`Invalid key length: ${key.length} bytes
|
throw new OperationError(`Invalid key length: ${key.length} bytes
|
||||||
|
|
||||||
DES uses a key length of 8 bytes (64 bits).
|
DES uses a key length of 8 bytes (64 bits).`);
|
||||||
Triple DES uses a key length of 24 bytes (192 bits).`);
|
|
||||||
}
|
}
|
||||||
if (iv.length !== 8 && mode !== "ECB") {
|
if (iv.length !== 8 && mode !== "ECB") {
|
||||||
throw new OperationError(`Invalid IV length: ${iv.length} bytes
|
throw new OperationError(`Invalid IV length: ${iv.length} bytes
|
||||||
|
|
|
@ -22,7 +22,7 @@ class DESEncrypt extends Operation {
|
||||||
|
|
||||||
this.name = "DES Encrypt";
|
this.name = "DES Encrypt";
|
||||||
this.module = "Ciphers";
|
this.module = "Ciphers";
|
||||||
this.description = "DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.<br><br><b>Key:</b> DES uses a key length of 8 bytes (64 bits).<br>Triple DES uses a key length of 24 bytes (192 bits).<br><br>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.";
|
this.description = "DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.<br><br><b>Key:</b> DES uses a key length of 8 bytes (64 bits).<br><br>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Data_Encryption_Standard";
|
this.infoURL = "https://wikipedia.org/wiki/Data_Encryption_Standard";
|
||||||
this.inputType = "string";
|
this.inputType = "string";
|
||||||
this.outputType = "string";
|
this.outputType = "string";
|
||||||
|
@ -70,8 +70,7 @@ class DESEncrypt extends Operation {
|
||||||
if (key.length !== 8) {
|
if (key.length !== 8) {
|
||||||
throw new OperationError(`Invalid key length: ${key.length} bytes
|
throw new OperationError(`Invalid key length: ${key.length} bytes
|
||||||
|
|
||||||
DES uses a key length of 8 bytes (64 bits).
|
DES uses a key length of 8 bytes (64 bits).`);
|
||||||
Triple DES uses a key length of 24 bytes (192 bits).`);
|
|
||||||
}
|
}
|
||||||
if (iv.length !== 8 && mode !== "ECB") {
|
if (iv.length !== 8 && mode !== "ECB") {
|
||||||
throw new OperationError(`Invalid IV length: ${iv.length} bytes
|
throw new OperationError(`Invalid IV length: ${iv.length} bytes
|
||||||
|
|
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}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
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,
|
keySize = args[1] / 32,
|
||||||
iterations = args[2],
|
iterations = args[2],
|
||||||
hasher = args[3],
|
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]
|
key = CryptoJS.EvpKDF(passphrase, salt, { // lgtm [js/insufficient-password-hash]
|
||||||
keySize: keySize,
|
keySize: keySize,
|
||||||
hasher: CryptoJS.algo[hasher],
|
hasher: CryptoJS.algo[hasher],
|
||||||
|
|
|
@ -119,9 +119,9 @@ class Diff extends Operation {
|
||||||
|
|
||||||
for (let i = 0; i < diff.length; i++) {
|
for (let i = 0; i < diff.length; i++) {
|
||||||
if (diff[i].added) {
|
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) {
|
} 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) {
|
} else if (!showSubtraction) {
|
||||||
output += Utils.escapeHtml(diff[i].value);
|
output += Utils.escapeHtml(diff[i].value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import jimp from "jimp";
|
import Jimp from "jimp/es/index.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image Dither operation
|
* Image Dither operation
|
||||||
|
@ -44,7 +44,7 @@ class DitherImage extends Operation {
|
||||||
|
|
||||||
let image;
|
let image;
|
||||||
try {
|
try {
|
||||||
image = await jimp.read(input);
|
image = await Jimp.read(input);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error loading image. (${err})`);
|
throw new OperationError(`Error loading image. (${err})`);
|
||||||
}
|
}
|
||||||
|
@ -55,9 +55,9 @@ class DitherImage extends Operation {
|
||||||
|
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.getMIME() === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
79
src/core/operations/DropNthBytes.mjs
Normal file
79
src/core/operations/DropNthBytes.mjs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
/**
|
||||||
|
* @author Oshawk [oshawk@protonmail.com]
|
||||||
|
* @copyright Crown Copyright 2019
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop nth bytes operation
|
||||||
|
*/
|
||||||
|
class DropNthBytes extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DropNthBytes constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Drop nth bytes";
|
||||||
|
this.module = "Default";
|
||||||
|
this.description = "Drops every nth byte starting with a given byte.";
|
||||||
|
this.infoURL = "";
|
||||||
|
this.inputType = "byteArray";
|
||||||
|
this.outputType = "byteArray";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
name: "Drop every",
|
||||||
|
type: "number",
|
||||||
|
value: 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Starting at",
|
||||||
|
type: "number",
|
||||||
|
value: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Apply to each line",
|
||||||
|
type: "boolean",
|
||||||
|
value: false
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {byteArray} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {byteArray}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const n = args[0];
|
||||||
|
const start = args[1];
|
||||||
|
const eachLine = args[2];
|
||||||
|
|
||||||
|
if (parseInt(n, 10) !== n || n <= 0) {
|
||||||
|
throw new OperationError("'Drop every' must be a positive integer.");
|
||||||
|
}
|
||||||
|
if (parseInt(start, 10) !== start || start < 0) {
|
||||||
|
throw new OperationError("'Starting at' must be a positive or zero integer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset = 0;
|
||||||
|
const output = [];
|
||||||
|
for (let i = 0; i < input.length; i++) {
|
||||||
|
if (eachLine && input[i] === 0x0a) {
|
||||||
|
output.push(0x0a);
|
||||||
|
offset = i + 1;
|
||||||
|
} else if (i - offset < start || (i - (start + offset)) % n !== 0) {
|
||||||
|
output.push(input[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DropNthBytes;
|
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;
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Operation from "../Operation.mjs";
|
import Operation from "../Operation.mjs";
|
||||||
import { search, DOMAIN_REGEX } from "../lib/Extract.mjs";
|
import { search, DOMAIN_REGEX, DMARC_DOMAIN_REGEX } from "../lib/Extract.mjs";
|
||||||
import { caseInsensitiveSort } from "../lib/Sort.mjs";
|
import { caseInsensitiveSort } from "../lib/Sort.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -39,6 +39,11 @@ class ExtractDomains extends Operation {
|
||||||
name: "Unique",
|
name: "Unique",
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
value: false
|
value: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Underscore (DMARC, DKIM, etc)",
|
||||||
|
type: "boolean",
|
||||||
|
value: false
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -49,11 +54,11 @@ class ExtractDomains extends Operation {
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const [displayTotal, sort, unique] = args;
|
const [displayTotal, sort, unique, dmarc] = args;
|
||||||
|
|
||||||
const results = search(
|
const results = search(
|
||||||
input,
|
input,
|
||||||
DOMAIN_REGEX,
|
dmarc ? DMARC_DOMAIN_REGEX : DOMAIN_REGEX,
|
||||||
null,
|
null,
|
||||||
sort ? caseInsensitiveSort : null,
|
sort ? caseInsensitiveSort : null,
|
||||||
unique
|
unique
|
||||||
|
|
|
@ -39,7 +39,7 @@ class ExtractFiles extends Operation {
|
||||||
${supportedExts.join("</li><li>")}
|
${supportedExts.join("</li><li>")}
|
||||||
</li>
|
</li>
|
||||||
</ul>Minimum File Size can be used to prune small false positives.`;
|
</ul>Minimum File Size can be used to prune small false positives.`;
|
||||||
this.infoURL = "https://forensicswiki.xyz/wiki/index.php?title=File_Carving";
|
this.infoURL = "https://forensics.wiki/file_carving";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "ArrayBuffer";
|
||||||
this.outputType = "List<File>";
|
this.outputType = "List<File>";
|
||||||
this.presentType = "html";
|
this.presentType = "html";
|
||||||
|
|
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;
|
|
@ -66,7 +66,7 @@ class ExtractIPAddresses extends Operation {
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const [includeIpv4, includeIpv6, removeLocal, displayTotal, sort, unique] = args,
|
const [includeIpv4, includeIpv6, removeLocal, displayTotal, sort, unique] = args,
|
||||||
ipv4 = "(?:(?:\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d|\\d)(?:\\/\\d{1,2})?",
|
ipv4 = "(?:(?:\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d|\\d)(?:\\/\\d{1,2})?",
|
||||||
ipv6 = "((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})((([\\dA-F]{1,4}((?!\\3)::|:\\b|(?![\\dA-F])))|(?!\\2\\3)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})";
|
ipv6 = "((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})(([\\dA-F]{1,4}((?!\\3)::|:\\b|(?![\\dA-F])))|(?!\\2\\3)){2}";
|
||||||
let ips = "";
|
let ips = "";
|
||||||
|
|
||||||
if (includeIpv4 && includeIpv6) {
|
if (includeIpv4 && includeIpv6) {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||||
import Utils from "../Utils.mjs";
|
import Utils from "../Utils.mjs";
|
||||||
import { fromBinary } from "../lib/Binary.mjs";
|
import { fromBinary } from "../lib/Binary.mjs";
|
||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import jimp from "jimp";
|
import Jimp from "jimp/es/index.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract LSB operation
|
* Extract LSB operation
|
||||||
|
@ -73,7 +73,7 @@ class ExtractLSB extends Operation {
|
||||||
const bit = 7 - args.pop(),
|
const bit = 7 - args.pop(),
|
||||||
pixelOrder = args.pop(),
|
pixelOrder = args.pop(),
|
||||||
colours = args.filter(option => option !== "").map(option => COLOUR_OPTIONS.indexOf(option)),
|
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,
|
width = parsedImage.bitmap.width,
|
||||||
height = parsedImage.bitmap.height,
|
height = parsedImage.bitmap.height,
|
||||||
rgba = parsedImage.bitmap.data;
|
rgba = parsedImage.bitmap.data;
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
import Operation from "../Operation.mjs";
|
import Operation from "../Operation.mjs";
|
||||||
import OperationError from "../errors/OperationError.mjs";
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
import { isImage } from "../lib/FileType.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";
|
import {RGBA_DELIM_OPTIONS} from "../lib/Delim.mjs";
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ class ExtractRGBA extends Operation {
|
||||||
|
|
||||||
const delimiter = args[0],
|
const delimiter = args[0],
|
||||||
includeAlpha = args[1],
|
includeAlpha = args[1],
|
||||||
parsedImage = await jimp.read(input);
|
parsedImage = await Jimp.read(input);
|
||||||
|
|
||||||
let bitmap = parsedImage.bitmap.data;
|
let bitmap = parsedImage.bitmap.data;
|
||||||
bitmap = includeAlpha ? bitmap : bitmap.filter((val, idx) => idx % 4 !== 3);
|
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;
|
94
src/core/operations/FileTree.mjs
Normal file
94
src/core/operations/FileTree.mjs
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
/**
|
||||||
|
* @author sw5678
|
||||||
|
* @copyright Crown Copyright 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unique operation
|
||||||
|
*/
|
||||||
|
class FileTree extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unique constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "File Tree";
|
||||||
|
this.module = "Default";
|
||||||
|
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 = [
|
||||||
|
{
|
||||||
|
name: "File Path Delimiter",
|
||||||
|
type: "binaryString",
|
||||||
|
value: "/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Delimiter",
|
||||||
|
type: "option",
|
||||||
|
value: INPUT_DELIM_OPTIONS
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
|
||||||
|
// Set up arrow and pipe for nice output display
|
||||||
|
const ARROW = "|---";
|
||||||
|
const PIPE = "| ";
|
||||||
|
|
||||||
|
// Get args from input
|
||||||
|
const fileDelim = args[0];
|
||||||
|
const entryDelim = Utils.charRep(args[1]);
|
||||||
|
|
||||||
|
// Store path to print
|
||||||
|
const completedList = [];
|
||||||
|
const printList = [];
|
||||||
|
|
||||||
|
// Loop through all entries
|
||||||
|
const filePaths = input.split(entryDelim).unique().sort();
|
||||||
|
for (let i = 0; i < filePaths.length; i++) {
|
||||||
|
// Split by file delimiter
|
||||||
|
let path = filePaths[i].split(fileDelim);
|
||||||
|
|
||||||
|
if (path[0] === "") {
|
||||||
|
path = path.slice(1, path.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let j = 0; j < path.length; j++) {
|
||||||
|
let printLine;
|
||||||
|
let key;
|
||||||
|
if (j === 0) {
|
||||||
|
printLine = path[j];
|
||||||
|
key = path[j];
|
||||||
|
} else {
|
||||||
|
printLine = PIPE.repeat(j-1) + ARROW + path[j];
|
||||||
|
key = path.slice(0, j+1).join("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see we have already added that path
|
||||||
|
if (!completedList.includes(key)) {
|
||||||
|
completedList.push(key);
|
||||||
|
printList.push(printLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return printList.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FileTree;
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import jimp from "jimp";
|
import Jimp from "jimp/es/index.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flip Image operation
|
* Flip Image operation
|
||||||
|
@ -51,7 +51,7 @@ class FlipImage extends Operation {
|
||||||
|
|
||||||
let image;
|
let image;
|
||||||
try {
|
try {
|
||||||
image = await jimp.read(input);
|
image = await Jimp.read(input);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error loading image. (${err})`);
|
throw new OperationError(`Error loading image. (${err})`);
|
||||||
}
|
}
|
||||||
|
@ -69,9 +69,9 @@ class FlipImage extends Operation {
|
||||||
|
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.getMIME() === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -60,7 +60,7 @@ class FromBase58 extends Operation {
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
let alphabet = args[0] || ALPHABET_OPTIONS[0].value;
|
let alphabet = args[0] || ALPHABET_OPTIONS[0].value;
|
||||||
const removeNonAlphaChars = args[1] === undefined ? true : args[1],
|
const removeNonAlphaChars = args[1] === undefined ? true : args[1],
|
||||||
result = [0];
|
result = [];
|
||||||
|
|
||||||
alphabet = Utils.expandAlphRange(alphabet).join("");
|
alphabet = Utils.expandAlphRange(alphabet).join("");
|
||||||
|
|
||||||
|
@ -87,11 +87,9 @@ class FromBase58 extends Operation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let carry = result[0] * 58 + index;
|
let carry = index;
|
||||||
result[0] = carry & 0xFF;
|
|
||||||
carry = carry >> 8;
|
|
||||||
|
|
||||||
for (let i = 1; i < result.length; i++) {
|
for (let i = 0; i < result.length; i++) {
|
||||||
carry += result[i] * 58;
|
carry += result[i] * 58;
|
||||||
result[i] = carry & 0xFF;
|
result[i] = carry & 0xFF;
|
||||||
carry = carry >> 8;
|
carry = carry >> 8;
|
||||||
|
|
55
src/core/operations/FromBase92.mjs
Normal file
55
src/core/operations/FromBase92.mjs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
/**
|
||||||
|
* @author sg5506844 [sg5506844@gmail.com]
|
||||||
|
* @copyright Crown Copyright 2021
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { base92Ord } from "../lib/Base92.mjs";
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From Base92 operation
|
||||||
|
*/
|
||||||
|
class FromBase92 extends Operation {
|
||||||
|
/**
|
||||||
|
* FromBase92 constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "From Base92";
|
||||||
|
this.module = "Default";
|
||||||
|
this.description = "Base92 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers.";
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "byteArray";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {byteArray}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const res = [];
|
||||||
|
let bitString = "";
|
||||||
|
|
||||||
|
for (let i = 0; i < input.length; i += 2) {
|
||||||
|
if (i + 1 !== input.length) {
|
||||||
|
const x = base92Ord(input[i]) * 91 + base92Ord(input[i + 1]);
|
||||||
|
bitString += x.toString(2).padStart(13, "0");
|
||||||
|
} else {
|
||||||
|
const x = base92Ord(input[i]);
|
||||||
|
bitString += x.toString(2).padStart(6, "0");
|
||||||
|
}
|
||||||
|
while (bitString.length >= 8) {
|
||||||
|
res.push(parseInt(bitString.slice(0, 8), 2));
|
||||||
|
bitString = bitString.slice(8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FromBase92;
|
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;
|
84
src/core/operations/FromModhex.mjs
Normal file
84
src/core/operations/FromModhex.mjs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
/**
|
||||||
|
* @author linuxgemini [ilteris@asenkron.com.tr]
|
||||||
|
* @copyright Crown Copyright 2024
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import { FROM_MODHEX_DELIM_OPTIONS, fromModhex } from "../lib/Modhex.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From Modhex operation
|
||||||
|
*/
|
||||||
|
class FromModhex extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FromModhex constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "From Modhex";
|
||||||
|
this.module = "Default";
|
||||||
|
this.description = "Converts a modhex byte string back into its raw value.";
|
||||||
|
this.infoURL = "https://en.wikipedia.org/wiki/YubiKey#ModHex";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "byteArray";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
name: "Delimiter",
|
||||||
|
type: "option",
|
||||||
|
value: FROM_MODHEX_DELIM_OPTIONS
|
||||||
|
}
|
||||||
|
];
|
||||||
|
this.checks = [
|
||||||
|
{
|
||||||
|
pattern: "^(?:[cbdefghijklnrtuv]{2})+$",
|
||||||
|
flags: "i",
|
||||||
|
args: ["None"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "^[cbdefghijklnrtuv]{2}(?: [cbdefghijklnrtuv]{2})*$",
|
||||||
|
flags: "i",
|
||||||
|
args: ["Space"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "^[cbdefghijklnrtuv]{2}(?:,[cbdefghijklnrtuv]{2})*$",
|
||||||
|
flags: "i",
|
||||||
|
args: ["Comma"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "^[cbdefghijklnrtuv]{2}(?:;[cbdefghijklnrtuv]{2})*$",
|
||||||
|
flags: "i",
|
||||||
|
args: ["Semi-colon"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "^[cbdefghijklnrtuv]{2}(?::[cbdefghijklnrtuv]{2})*$",
|
||||||
|
flags: "i",
|
||||||
|
args: ["Colon"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "^[cbdefghijklnrtuv]{2}(?:\\n[cbdefghijklnrtuv]{2})*$",
|
||||||
|
flags: "i",
|
||||||
|
args: ["Line feed"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "^[cbdefghijklnrtuv]{2}(?:\\r\\n[cbdefghijklnrtuv]{2})*$",
|
||||||
|
flags: "i",
|
||||||
|
args: ["CRLF"]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {byteArray}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const delim = args[0] || "Auto";
|
||||||
|
return fromModhex(input, delim, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FromModhex;
|
|
@ -55,22 +55,19 @@ class GOSTDecrypt extends Operation {
|
||||||
type: "argSelector",
|
type: "argSelector",
|
||||||
value: [
|
value: [
|
||||||
{
|
{
|
||||||
name: "GOST 28147 (Magma, 1989)",
|
name: "GOST 28147 (1989)",
|
||||||
off: [5],
|
on: [5]
|
||||||
on: [6]
|
},
|
||||||
|
{
|
||||||
|
name: "GOST R 34.12 (Magma, 2015)",
|
||||||
|
off: [5]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "GOST R 34.12 (Kuznyechik, 2015)",
|
name: "GOST R 34.12 (Kuznyechik, 2015)",
|
||||||
on: [5],
|
off: [5]
|
||||||
off: [6]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "Block length",
|
|
||||||
type: "option",
|
|
||||||
value: ["64", "128"]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "sBox",
|
name: "sBox",
|
||||||
type: "option",
|
type: "option",
|
||||||
|
@ -100,14 +97,30 @@ class GOSTDecrypt extends Operation {
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
async run(input, args) {
|
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 key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option));
|
||||||
const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option));
|
const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option));
|
||||||
input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input));
|
input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input));
|
||||||
|
|
||||||
const versionNum = version === "GOST 28147 (Magma, 1989)" ? 1989 : 2015;
|
let blockLength, versionNum;
|
||||||
const blockLength = versionNum === 1989 ? 64 : parseInt(length, 10);
|
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 sBoxVal = versionNum === 1989 ? sBox : null;
|
||||||
|
|
||||||
const algorithm = {
|
const algorithm = {
|
||||||
|
|
|
@ -55,22 +55,19 @@ class GOSTEncrypt extends Operation {
|
||||||
type: "argSelector",
|
type: "argSelector",
|
||||||
value: [
|
value: [
|
||||||
{
|
{
|
||||||
name: "GOST 28147 (Magma, 1989)",
|
name: "GOST 28147 (1989)",
|
||||||
off: [5],
|
on: [5]
|
||||||
on: [6]
|
},
|
||||||
|
{
|
||||||
|
name: "GOST R 34.12 (Magma, 2015)",
|
||||||
|
off: [5]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "GOST R 34.12 (Kuznyechik, 2015)",
|
name: "GOST R 34.12 (Kuznyechik, 2015)",
|
||||||
on: [5],
|
off: [5]
|
||||||
off: [6]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "Block length",
|
|
||||||
type: "option",
|
|
||||||
value: ["64", "128"]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "sBox",
|
name: "sBox",
|
||||||
type: "option",
|
type: "option",
|
||||||
|
@ -100,14 +97,30 @@ class GOSTEncrypt extends Operation {
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
async run(input, args) {
|
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 key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option));
|
||||||
const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option));
|
const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option));
|
||||||
input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input));
|
input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input));
|
||||||
|
|
||||||
const versionNum = version === "GOST 28147 (Magma, 1989)" ? 1989 : 2015;
|
let blockLength, versionNum;
|
||||||
const blockLength = versionNum === 1989 ? 64 : parseInt(length, 10);
|
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 sBoxVal = versionNum === 1989 ? sBox : null;
|
||||||
|
|
||||||
const algorithm = {
|
const algorithm = {
|
||||||
|
|
|
@ -55,22 +55,19 @@ class GOSTKeyUnwrap extends Operation {
|
||||||
type: "argSelector",
|
type: "argSelector",
|
||||||
value: [
|
value: [
|
||||||
{
|
{
|
||||||
name: "GOST 28147 (Magma, 1989)",
|
name: "GOST 28147 (1989)",
|
||||||
off: [5],
|
on: [5]
|
||||||
on: [6]
|
},
|
||||||
|
{
|
||||||
|
name: "GOST R 34.12 (Magma, 2015)",
|
||||||
|
off: [5]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "GOST R 34.12 (Kuznyechik, 2015)",
|
name: "GOST R 34.12 (Kuznyechik, 2015)",
|
||||||
on: [5],
|
off: [5]
|
||||||
off: [6]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "Block length",
|
|
||||||
type: "option",
|
|
||||||
value: ["64", "128"]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "sBox",
|
name: "sBox",
|
||||||
type: "option",
|
type: "option",
|
||||||
|
@ -90,14 +87,30 @@ class GOSTKeyUnwrap extends Operation {
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
async run(input, args) {
|
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 key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option));
|
||||||
const ukm = toHexFast(Utils.convertToByteArray(ukmObj.string, ukmObj.option));
|
const ukm = toHexFast(Utils.convertToByteArray(ukmObj.string, ukmObj.option));
|
||||||
input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input));
|
input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input));
|
||||||
|
|
||||||
const versionNum = version === "GOST 28147 (Magma, 1989)" ? 1989 : 2015;
|
let blockLength, versionNum;
|
||||||
const blockLength = versionNum === 1989 ? 64 : parseInt(length, 10);
|
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 sBoxVal = versionNum === 1989 ? sBox : null;
|
||||||
|
|
||||||
const algorithm = {
|
const algorithm = {
|
||||||
|
|
|
@ -55,22 +55,19 @@ class GOSTKeyWrap extends Operation {
|
||||||
type: "argSelector",
|
type: "argSelector",
|
||||||
value: [
|
value: [
|
||||||
{
|
{
|
||||||
name: "GOST 28147 (Magma, 1989)",
|
name: "GOST 28147 (1989)",
|
||||||
off: [5],
|
on: [5]
|
||||||
on: [6]
|
},
|
||||||
|
{
|
||||||
|
name: "GOST R 34.12 (Magma, 2015)",
|
||||||
|
off: [5]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "GOST R 34.12 (Kuznyechik, 2015)",
|
name: "GOST R 34.12 (Kuznyechik, 2015)",
|
||||||
on: [5],
|
off: [5]
|
||||||
off: [6]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "Block length",
|
|
||||||
type: "option",
|
|
||||||
value: ["64", "128"]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "sBox",
|
name: "sBox",
|
||||||
type: "option",
|
type: "option",
|
||||||
|
@ -90,14 +87,30 @@ class GOSTKeyWrap extends Operation {
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
async run(input, args) {
|
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 key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option));
|
||||||
const ukm = toHexFast(Utils.convertToByteArray(ukmObj.string, ukmObj.option));
|
const ukm = toHexFast(Utils.convertToByteArray(ukmObj.string, ukmObj.option));
|
||||||
input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input));
|
input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input));
|
||||||
|
|
||||||
const versionNum = version === "GOST 28147 (Magma, 1989)" ? 1989 : 2015;
|
let blockLength, versionNum;
|
||||||
const blockLength = versionNum === 1989 ? 64 : parseInt(length, 10);
|
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 sBoxVal = versionNum === 1989 ? sBox : null;
|
||||||
|
|
||||||
const algorithm = {
|
const algorithm = {
|
||||||
|
|
|
@ -55,22 +55,19 @@ class GOSTSign extends Operation {
|
||||||
type: "argSelector",
|
type: "argSelector",
|
||||||
value: [
|
value: [
|
||||||
{
|
{
|
||||||
name: "GOST 28147 (Magma, 1989)",
|
name: "GOST 28147 (1989)",
|
||||||
off: [5],
|
on: [5]
|
||||||
on: [6]
|
},
|
||||||
|
{
|
||||||
|
name: "GOST R 34.12 (Magma, 2015)",
|
||||||
|
off: [5]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "GOST R 34.12 (Kuznyechik, 2015)",
|
name: "GOST R 34.12 (Kuznyechik, 2015)",
|
||||||
on: [5],
|
off: [5]
|
||||||
off: [6]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "Block length",
|
|
||||||
type: "option",
|
|
||||||
value: ["64", "128"]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "sBox",
|
name: "sBox",
|
||||||
type: "option",
|
type: "option",
|
||||||
|
@ -93,14 +90,30 @@ class GOSTSign extends Operation {
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
async run(input, args) {
|
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 key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option));
|
||||||
const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option));
|
const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option));
|
||||||
input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input));
|
input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input));
|
||||||
|
|
||||||
const versionNum = version === "GOST 28147 (Magma, 1989)" ? 1989 : 2015;
|
let blockLength, versionNum;
|
||||||
const blockLength = versionNum === 1989 ? 64 : parseInt(length, 10);
|
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 sBoxVal = versionNum === 1989 ? sBox : null;
|
||||||
|
|
||||||
const algorithm = {
|
const algorithm = {
|
||||||
|
|
|
@ -56,22 +56,19 @@ class GOSTVerify extends Operation {
|
||||||
type: "argSelector",
|
type: "argSelector",
|
||||||
value: [
|
value: [
|
||||||
{
|
{
|
||||||
name: "GOST 28147 (Magma, 1989)",
|
name: "GOST 28147 (1989)",
|
||||||
off: [5],
|
on: [5]
|
||||||
on: [6]
|
},
|
||||||
|
{
|
||||||
|
name: "GOST R 34.12 (Magma, 2015)",
|
||||||
|
off: [5]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "GOST R 34.12 (Kuznyechik, 2015)",
|
name: "GOST R 34.12 (Kuznyechik, 2015)",
|
||||||
on: [5],
|
off: [5]
|
||||||
off: [6]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "Block length",
|
|
||||||
type: "option",
|
|
||||||
value: ["64", "128"]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "sBox",
|
name: "sBox",
|
||||||
type: "option",
|
type: "option",
|
||||||
|
@ -86,15 +83,31 @@ class GOSTVerify extends Operation {
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
async run(input, args) {
|
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 key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option));
|
||||||
const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option));
|
const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option));
|
||||||
const mac = toHexFast(Utils.convertToByteArray(macObj.string, macObj.option));
|
const mac = toHexFast(Utils.convertToByteArray(macObj.string, macObj.option));
|
||||||
input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input));
|
input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input));
|
||||||
|
|
||||||
const versionNum = version === "GOST 28147 (Magma, 1989)" ? 1989 : 2015;
|
let blockLength, versionNum;
|
||||||
const blockLength = versionNum === 1989 ? 64 : parseInt(length, 10);
|
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 sBoxVal = versionNum === 1989 ? sBox : null;
|
||||||
|
|
||||||
const algorithm = {
|
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;
|
|
@ -5,16 +5,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Operation from "../Operation.mjs";
|
import Operation from "../Operation.mjs";
|
||||||
import otp from "otp";
|
import * as OTPAuth from "otpauth";
|
||||||
import ToBase32 from "./ToBase32.mjs";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate HOTP operation
|
* Generate HOTP operation
|
||||||
*/
|
*/
|
||||||
class GenerateHOTP extends Operation {
|
class GenerateHOTP extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GenerateHOTP constructor
|
*
|
||||||
*/
|
*/
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
@ -31,11 +29,6 @@ class GenerateHOTP extends Operation {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"value": ""
|
"value": ""
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "Key size",
|
|
||||||
"type": "number",
|
|
||||||
"value": 32
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "Code length",
|
"name": "Code length",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
@ -50,21 +43,26 @@ class GenerateHOTP extends Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {ArrayBuffer} input
|
*
|
||||||
* @param {Object[]} args
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const otpObj = otp({
|
const secretStr = new TextDecoder("utf-8").decode(input).trim();
|
||||||
name: args[0],
|
const secret = secretStr ? secretStr.toUpperCase().replace(/\s+/g, "") : "";
|
||||||
keySize: args[1],
|
|
||||||
codeLength: args[2],
|
|
||||||
secret: (new ToBase32).run(input, []).split("=")[0],
|
|
||||||
});
|
|
||||||
const counter = args[3];
|
|
||||||
return `URI: ${otpObj.hotpURL}\n\nPassword: ${otpObj.hotp(counter)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const hotp = new OTPAuth.HOTP({
|
||||||
|
issuer: "",
|
||||||
|
label: args[0],
|
||||||
|
algorithm: "SHA1",
|
||||||
|
digits: args[1],
|
||||||
|
counter: args[2],
|
||||||
|
secret: OTPAuth.Secret.fromBase32(secret)
|
||||||
|
});
|
||||||
|
|
||||||
|
const uri = hotp.toString();
|
||||||
|
const code = hotp.generate();
|
||||||
|
|
||||||
|
return `URI: ${uri}\n\nPassword: ${code}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default GenerateHOTP;
|
export default GenerateHOTP;
|
||||||
|
|
|
@ -10,7 +10,7 @@ import Utils from "../Utils.mjs";
|
||||||
import {isImage} from "../lib/FileType.mjs";
|
import {isImage} from "../lib/FileType.mjs";
|
||||||
import {toBase64} from "../lib/Base64.mjs";
|
import {toBase64} from "../lib/Base64.mjs";
|
||||||
import {isWorkerEnvironment} from "../Utils.mjs";
|
import {isWorkerEnvironment} from "../Utils.mjs";
|
||||||
import jimp from "jimp";
|
import Jimp from "jimp/es/index.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate Image operation
|
* Generate Image operation
|
||||||
|
@ -81,7 +81,7 @@ class GenerateImage extends Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
const height = Math.ceil(input.length / bytesPerPixel / width);
|
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())
|
if (isWorkerEnvironment())
|
||||||
self.sendStatusMessage("Generating image from data...");
|
self.sendStatusMessage("Generating image from data...");
|
||||||
|
@ -95,7 +95,7 @@ class GenerateImage extends Operation {
|
||||||
const y = Math.floor(index / width);
|
const y = Math.floor(index / width);
|
||||||
|
|
||||||
const value = curByte[k] === "0" ? 0xFF : 0x00;
|
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);
|
image.setPixelColor(pixel, x, y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,7 +139,7 @@ class GenerateImage extends Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const pixel = jimp.rgbaToInt(red, green, blue, alpha);
|
const pixel = Jimp.rgbaToInt(red, green, blue, alpha);
|
||||||
image.setPixelColor(pixel, x, y);
|
image.setPixelColor(pixel, x, y);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error while generating image from pixel values. (${err})`);
|
throw new OperationError(`Error while generating image from pixel values. (${err})`);
|
||||||
|
@ -151,11 +151,11 @@ class GenerateImage extends Operation {
|
||||||
if (isWorkerEnvironment())
|
if (isWorkerEnvironment())
|
||||||
self.sendStatusMessage("Scaling image...");
|
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 {
|
try {
|
||||||
const imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
const imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error generating image. (${err})`);
|
throw new OperationError(`Error generating image. (${err})`);
|
||||||
|
|
|
@ -5,20 +5,17 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Operation from "../Operation.mjs";
|
import Operation from "../Operation.mjs";
|
||||||
import otp from "otp";
|
import * as OTPAuth from "otpauth";
|
||||||
import ToBase32 from "./ToBase32.mjs";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate TOTP operation
|
* Generate TOTP operation
|
||||||
*/
|
*/
|
||||||
class GenerateTOTP extends Operation {
|
class GenerateTOTP extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GenerateTOTP constructor
|
*
|
||||||
*/
|
*/
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.name = "Generate TOTP";
|
this.name = "Generate TOTP";
|
||||||
this.module = "Default";
|
this.module = "Default";
|
||||||
this.description = "The Time-based One-Time Password algorithm (TOTP) is an algorithm that computes a one-time password from a shared secret key and the current time. It has been adopted as Internet Engineering Task Force standard RFC 6238, is the cornerstone of Initiative For Open Authentication (OAUTH), and is used in a number of two-factor authentication systems. A TOTP is an HOTP where the counter is the current time.<br><br>Enter the secret as the input or leave it blank for a random secret to be generated. T0 and T1 are in seconds.";
|
this.description = "The Time-based One-Time Password algorithm (TOTP) is an algorithm that computes a one-time password from a shared secret key and the current time. It has been adopted as Internet Engineering Task Force standard RFC 6238, is the cornerstone of Initiative For Open Authentication (OAUTH), and is used in a number of two-factor authentication systems. A TOTP is an HOTP where the counter is the current time.<br><br>Enter the secret as the input or leave it blank for a random secret to be generated. T0 and T1 are in seconds.";
|
||||||
|
@ -31,11 +28,6 @@ class GenerateTOTP extends Operation {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"value": ""
|
"value": ""
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "Key size",
|
|
||||||
"type": "number",
|
|
||||||
"value": 32
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "Code length",
|
"name": "Code length",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
@ -55,22 +47,27 @@ class GenerateTOTP extends Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {ArrayBuffer} input
|
*
|
||||||
* @param {Object[]} args
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const otpObj = otp({
|
const secretStr = new TextDecoder("utf-8").decode(input).trim();
|
||||||
name: args[0],
|
const secret = secretStr ? secretStr.toUpperCase().replace(/\s+/g, "") : "";
|
||||||
keySize: args[1],
|
|
||||||
codeLength: args[2],
|
|
||||||
secret: (new ToBase32).run(input, []).split("=")[0],
|
|
||||||
epoch: args[3],
|
|
||||||
timeSlice: args[4]
|
|
||||||
});
|
|
||||||
return `URI: ${otpObj.totpURL}\n\nPassword: ${otpObj.totp()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const totp = new OTPAuth.TOTP({
|
||||||
|
issuer: "",
|
||||||
|
label: args[0],
|
||||||
|
algorithm: "SHA1",
|
||||||
|
digits: args[1],
|
||||||
|
period: args[3],
|
||||||
|
epoch: args[2] * 1000, // Convert seconds to milliseconds
|
||||||
|
secret: OTPAuth.Secret.fromBase32(secret)
|
||||||
|
});
|
||||||
|
|
||||||
|
const uri = totp.toString();
|
||||||
|
const code = totp.generate();
|
||||||
|
|
||||||
|
return `URI: ${uri}\n\nPassword: ${code}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default GenerateTOTP;
|
export default GenerateTOTP;
|
||||||
|
|
209
src/core/operations/IPv6TransitionAddresses.mjs
Normal file
209
src/core/operations/IPv6TransitionAddresses.mjs
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
/**
|
||||||
|
* @author jb30795 [jb30795@proton.me]
|
||||||
|
* @copyright Crown Copyright 2024
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IPv6 Transition Addresses operation
|
||||||
|
*/
|
||||||
|
class IPv6TransitionAddresses extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IPv6TransitionAddresses constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "IPv6 Transition Addresses";
|
||||||
|
this.module = "Default";
|
||||||
|
this.description = "Converts IPv4 addresses to their IPv6 Transition addresses. IPv6 Transition addresses can also be converted back into their original IPv4 address. MAC addresses can also be converted into the EUI-64 format, this can them be appended to your IPv6 /64 range to obtain a full /128 address.<br><br>Transition technologies enable translation between IPv4 and IPv6 addresses or tunneling to allow traffic to pass through the incompatible network, allowing the two standards to coexist.<br><br>Only /24 ranges and currently handled. Remove headers to easily copy out results.";
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/IPv6_transition_mechanism";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Ignore ranges",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Remove headers",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": false
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const XOR = {"0": "2", "1": "3", "2": "0", "3": "1", "4": "6", "5": "7", "6": "4", "7": "5", "8": "a", "9": "b", "a": "8", "b": "9", "c": "e", "d": "f", "e": "c", "f": "d"};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to convert to hex
|
||||||
|
*/
|
||||||
|
function hexify(octet) {
|
||||||
|
return Number(octet).toString(16).padStart(2, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to convert Hex to Int
|
||||||
|
*/
|
||||||
|
function intify(hex) {
|
||||||
|
return parseInt(hex, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function converts IPv4 to IPv6 Transtion address
|
||||||
|
*/
|
||||||
|
function ipTransition(input, range) {
|
||||||
|
let output = "";
|
||||||
|
const HEXIP = input.split(".");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 6to4
|
||||||
|
*/
|
||||||
|
if (!args[1]) {
|
||||||
|
output += "6to4: ";
|
||||||
|
}
|
||||||
|
output += "2002:" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]);
|
||||||
|
if (range) {
|
||||||
|
output += "00::/40\n";
|
||||||
|
} else {
|
||||||
|
output += hexify(HEXIP[3]) + "::/48\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapped
|
||||||
|
*/
|
||||||
|
if (!args[1]) {
|
||||||
|
output += "IPv4 Mapped: ";
|
||||||
|
}
|
||||||
|
output += "::ffff:" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]);
|
||||||
|
if (range) {
|
||||||
|
output += "00/120\n";
|
||||||
|
} else {
|
||||||
|
output += hexify(HEXIP[3]) + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translated
|
||||||
|
*/
|
||||||
|
if (!args[1]) {
|
||||||
|
output += "IPv4 Translated: ";
|
||||||
|
}
|
||||||
|
output += "::ffff:0:" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]);
|
||||||
|
if (range) {
|
||||||
|
output += "00/120\n";
|
||||||
|
} else {
|
||||||
|
output += hexify(HEXIP[3]) + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nat64
|
||||||
|
*/
|
||||||
|
if (!args[1]) {
|
||||||
|
output += "Nat 64: ";
|
||||||
|
}
|
||||||
|
output += "64:ff9b::" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]);
|
||||||
|
if (range) {
|
||||||
|
output += "00/120\n";
|
||||||
|
} else {
|
||||||
|
output += hexify(HEXIP[3]) + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert MAC to EUI-64
|
||||||
|
*/
|
||||||
|
function macTransition(input) {
|
||||||
|
let output = "";
|
||||||
|
const MACPARTS = input.split(":");
|
||||||
|
if (!args[1]) {
|
||||||
|
output += "EUI-64 Interface ID: ";
|
||||||
|
}
|
||||||
|
const MAC = MACPARTS[0] + MACPARTS[1] + ":" + MACPARTS[2] + "ff:fe" + MACPARTS[3] + ":" + MACPARTS[4] + MACPARTS[5];
|
||||||
|
output += MAC.slice(0, 1) + XOR[MAC.slice(1, 2)] + MAC.slice(2);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert IPv6 address to its original IPv4 or MAC address
|
||||||
|
*/
|
||||||
|
function unTransition(input) {
|
||||||
|
let output = "";
|
||||||
|
let hextets = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 6to4
|
||||||
|
*/
|
||||||
|
if (input.startsWith("2002:")) {
|
||||||
|
if (!args[1]) {
|
||||||
|
output += "IPv4: ";
|
||||||
|
}
|
||||||
|
output += String(intify(input.slice(5, 7))) + "." + String(intify(input.slice(7, 9)))+ "." + String(intify(input.slice(10, 12)))+ "." + String(intify(input.slice(12, 14))) + "\n";
|
||||||
|
} else if (input.startsWith("::ffff:") || input.startsWith("0000:0000:0000:0000:0000:ffff:") || input.startsWith("::ffff:0000:") || input.startsWith("0000:0000:0000:0000:ffff:0000:") || input.startsWith("64:ff9b::") || input.startsWith("0064:ff9b:0000:0000:0000:0000:")) {
|
||||||
|
/**
|
||||||
|
* Mapped/Translated/Nat64
|
||||||
|
*/
|
||||||
|
hextets = /:([0-9a-z]{1,4}):[0-9a-z]{1,4}$/.exec(input)[1].padStart(4, "0") + /:([0-9a-z]{1,4})$/.exec(input)[1].padStart(4, "0");
|
||||||
|
if (!args[1]) {
|
||||||
|
output += "IPv4: ";
|
||||||
|
}
|
||||||
|
output += intify(hextets.slice(-8, -7) + hextets.slice(-7, -6)) + "." +intify(hextets.slice(-6, -5) + hextets.slice(-5, -4)) + "." +intify(hextets.slice(-4, -3) + hextets.slice(-3, -2)) + "." +intify(hextets.slice(-2, -1) + hextets.slice(-1,)) + "\n";
|
||||||
|
} else if (input.slice(-12, -7).toUpperCase() === "FF:FE") {
|
||||||
|
/**
|
||||||
|
* EUI-64
|
||||||
|
*/
|
||||||
|
if (!args[1]) {
|
||||||
|
output += "Mac Address: ";
|
||||||
|
}
|
||||||
|
const MAC = (input.slice(-19, -17) + ":" + input.slice(-17, -15) + ":" + input.slice(-14, -12) + ":" + input.slice(-7, -5) + ":" + input.slice(-4, -2) + ":" + input.slice(-2,)).toUpperCase();
|
||||||
|
output += MAC.slice(0, 1) + XOR[MAC.slice(1, 2)] + MAC.slice(2) + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main
|
||||||
|
*/
|
||||||
|
let output = "";
|
||||||
|
let inputs = input.split("\n");
|
||||||
|
// Remove blank rows
|
||||||
|
inputs = inputs.filter(Boolean);
|
||||||
|
|
||||||
|
for (let i = 0; i < inputs.length; i++) {
|
||||||
|
// if ignore ranges is checked and input is a range, skip
|
||||||
|
if ((args[0] && !inputs[i].includes("/")) || (!args[0])) {
|
||||||
|
if (/^[0-9]{1,3}(?:\.[0-9]{1,3}){3}$/.test(inputs[i])) {
|
||||||
|
output += ipTransition(inputs[i], false);
|
||||||
|
} else if (/\/24$/.test(inputs[i])) {
|
||||||
|
output += ipTransition(inputs[i], true);
|
||||||
|
} else if (/^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$/.test(inputs[i])) {
|
||||||
|
output += macTransition(inputs[i]);
|
||||||
|
} else if (/^((?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(?::[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(?:ffff(?::0{1,4}){0,1}:){0,1}(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])|(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/.test(inputs[i])) {
|
||||||
|
output += unTransition(inputs[i]);
|
||||||
|
} else {
|
||||||
|
output = "Enter compressed or expanded IPv6 address, IPv4 address or MAC Address.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IPv6TransitionAddresses;
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import jimp from "jimp";
|
import Jimp from "jimp/es/index.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image Brightness / Contrast operation
|
* Image Brightness / Contrast operation
|
||||||
|
@ -60,7 +60,7 @@ class ImageBrightnessContrast extends Operation {
|
||||||
|
|
||||||
let image;
|
let image;
|
||||||
try {
|
try {
|
||||||
image = await jimp.read(input);
|
image = await Jimp.read(input);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error loading image. (${err})`);
|
throw new OperationError(`Error loading image. (${err})`);
|
||||||
}
|
}
|
||||||
|
@ -78,9 +78,9 @@ class ImageBrightnessContrast extends Operation {
|
||||||
|
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.getMIME() === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import jimp from "jimp";
|
import Jimp from "jimp/es/index.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image Filter operation
|
* Image Filter operation
|
||||||
|
@ -54,7 +54,7 @@ class ImageFilter extends Operation {
|
||||||
|
|
||||||
let image;
|
let image;
|
||||||
try {
|
try {
|
||||||
image = await jimp.read(input);
|
image = await Jimp.read(input);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error loading image. (${err})`);
|
throw new OperationError(`Error loading image. (${err})`);
|
||||||
}
|
}
|
||||||
|
@ -69,9 +69,9 @@ class ImageFilter extends Operation {
|
||||||
|
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.getMIME() === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import jimp from "jimp";
|
import Jimp from "jimp/es/index.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image Hue/Saturation/Lightness operation
|
* Image Hue/Saturation/Lightness operation
|
||||||
|
@ -68,7 +68,7 @@ class ImageHueSaturationLightness extends Operation {
|
||||||
|
|
||||||
let image;
|
let image;
|
||||||
try {
|
try {
|
||||||
image = await jimp.read(input);
|
image = await Jimp.read(input);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error loading image. (${err})`);
|
throw new OperationError(`Error loading image. (${err})`);
|
||||||
}
|
}
|
||||||
|
@ -106,9 +106,9 @@ class ImageHueSaturationLightness extends Operation {
|
||||||
|
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.getMIME() === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import jimp from "jimp";
|
import Jimp from "jimp/es/index.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image Opacity operation
|
* Image Opacity operation
|
||||||
|
@ -53,7 +53,7 @@ class ImageOpacity extends Operation {
|
||||||
|
|
||||||
let image;
|
let image;
|
||||||
try {
|
try {
|
||||||
image = await jimp.read(input);
|
image = await Jimp.read(input);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error loading image. (${err})`);
|
throw new OperationError(`Error loading image. (${err})`);
|
||||||
}
|
}
|
||||||
|
@ -64,9 +64,9 @@ class ImageOpacity extends Operation {
|
||||||
|
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.getMIME() === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||||
import { isImage } from "../lib/FileType.mjs";
|
import { isImage } from "../lib/FileType.mjs";
|
||||||
import { toBase64 } from "../lib/Base64.mjs";
|
import { toBase64 } from "../lib/Base64.mjs";
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import jimp from "jimp";
|
import Jimp from "jimp/es/index.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invert Image operation
|
* Invert Image operation
|
||||||
|
@ -44,7 +44,7 @@ class InvertImage extends Operation {
|
||||||
|
|
||||||
let image;
|
let image;
|
||||||
try {
|
try {
|
||||||
image = await jimp.read(input);
|
image = await Jimp.read(input);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error loading image. (${err})`);
|
throw new OperationError(`Error loading image. (${err})`);
|
||||||
}
|
}
|
||||||
|
@ -55,9 +55,9 @@ class InvertImage extends Operation {
|
||||||
|
|
||||||
let imageBuffer;
|
let imageBuffer;
|
||||||
if (image.getMIME() === "image/gif") {
|
if (image.getMIME() === "image/gif") {
|
||||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||||
} else {
|
} else {
|
||||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||||
}
|
}
|
||||||
return imageBuffer.buffer;
|
return imageBuffer.buffer;
|
||||||
} catch (err) {
|
} 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",
|
name: "Result delimiter",
|
||||||
type: "binaryShortString",
|
type: "binaryShortString",
|
||||||
value: "\\n"
|
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}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const [query, delimiter, preventEval] = args;
|
const [query, delimiter] = args;
|
||||||
let results, jsonObj;
|
let results, jsonObj;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -63,8 +57,7 @@ class JPathExpression extends Operation {
|
||||||
try {
|
try {
|
||||||
results = JSONPath({
|
results = JSONPath({
|
||||||
path: query,
|
path: query,
|
||||||
json: jsonObj,
|
json: jsonObj
|
||||||
preventEval: preventEval
|
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Invalid JPath expression: ${err.message}`);
|
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",
|
name: "Signing algorithm",
|
||||||
type: "option",
|
type: "option",
|
||||||
value: JWT_ALGORITHMS
|
value: JWT_ALGORITHMS
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Header",
|
||||||
|
type: "text",
|
||||||
|
value: "{}"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -46,11 +51,12 @@ class JWTSign extends Operation {
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const [key, algorithm] = args;
|
const [key, algorithm, header] = args;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return jwt.sign(input, key, {
|
return jwt.sign(input, key, {
|
||||||
algorithm: algorithm === "None" ? "none" : algorithm
|
algorithm: algorithm === "None" ? "none" : algorithm,
|
||||||
|
header: JSON.parse(header || "{}")
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} 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.
|
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.name = "JWT Verify";
|
||||||
this.module = "Crypto";
|
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.infoURL = "https://wikipedia.org/wiki/JSON_Web_Token";
|
||||||
this.inputType = "string";
|
this.inputType = "string";
|
||||||
this.outputType = "JSON";
|
this.outputType = "JSON";
|
||||||
|
|
41
src/core/operations/LZNT1Decompress.mjs
Normal file
41
src/core/operations/LZNT1Decompress.mjs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
/**
|
||||||
|
* @author 0xThiebaut [thiebaut.dev]
|
||||||
|
* @copyright Crown Copyright 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import {decompress} from "../lib/LZNT1.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LZNT1 Decompress operation
|
||||||
|
*/
|
||||||
|
class LZNT1Decompress extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LZNT1 Decompress constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "LZNT1 Decompress";
|
||||||
|
this.module = "Compression";
|
||||||
|
this.description = "Decompresses data using the LZNT1 algorithm.<br><br>Similar to the Windows API <code>RtlDecompressBuffer</code>.";
|
||||||
|
this.infoURL = "https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-xca/5655f4a3-6ba4-489b-959f-e1f407c52f15";
|
||||||
|
this.inputType = "byteArray";
|
||||||
|
this.outputType = "byteArray";
|
||||||
|
this.args = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {byteArray} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {byteArray}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
return decompress(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LZNT1Decompress;
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue