Merge branch 'master' into master
This commit is contained in:
commit
6c30c9c6b8
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",
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": 9,
|
|
||||||
"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
|
14
.github/workflows/codeql.yml
vendored
14
.github/workflows/codeql.yml
vendored
@ -1,11 +1,13 @@
|
|||||||
name: "CodeQL Analysis"
|
name: "CodeQL Analysis"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
pull_request:
|
pull_request:
|
||||||
# The branches below must be a subset of the branches above
|
# The branches below must be a subset of the branches above
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
|
types: [synchronize, opened, reopened]
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '22 17 * * 5'
|
- cron: '22 17 * * 5'
|
||||||
|
|
||||||
@ -13,6 +15,10 @@ jobs:
|
|||||||
analyze:
|
analyze:
|
||||||
name: Analyze
|
name: Analyze
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@ -21,12 +27,14 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v1
|
uses: github/codeql-action/init@v2
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v1
|
uses: github/codeql-action/analyze@v2
|
||||||
|
with:
|
||||||
|
category: "/language:${{matrix.language}}"
|
||||||
|
17
.github/workflows/master.yml
vendored
17
.github/workflows/master.yml
vendored
@ -1,6 +1,7 @@
|
|||||||
name: "Master Build, Test & Deploy"
|
name: "Master Build, Test & Deploy"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
@ -9,12 +10,12 @@ jobs:
|
|||||||
main:
|
main:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Set node version
|
- name: Set node version
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: '10.x'
|
node-version: '18.x'
|
||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
run: |
|
run: |
|
||||||
@ -27,18 +28,20 @@ jobs:
|
|||||||
- name: Unit Tests
|
- name: Unit Tests
|
||||||
run: |
|
run: |
|
||||||
npm test
|
npm test
|
||||||
npx grunt testnodeconsumer
|
npm run testnodeconsumer
|
||||||
|
|
||||||
- name: Production Build
|
- name: Production Build
|
||||||
if: success()
|
if: success()
|
||||||
run: npx grunt prod
|
run: npx grunt prod --msg="Version 10 is here! Read about the new features <a href='https://github.com/gchq/CyberChef/wiki/Character-encoding,-EOL-separators,-and-editor-features'>here</a>"
|
||||||
|
|
||||||
- name: Generate sitemap
|
- name: Generate sitemap
|
||||||
run: npx grunt exec:sitemap
|
run: npx grunt exec:sitemap
|
||||||
|
|
||||||
- name: UI Tests
|
- name: UI Tests
|
||||||
if: success()
|
if: success()
|
||||||
run: xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
|
run: |
|
||||||
|
sudo apt-get install xvfb
|
||||||
|
xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
|
||||||
|
|
||||||
- name: Prepare for GitHub Pages
|
- name: Prepare for GitHub Pages
|
||||||
if: success()
|
if: success()
|
||||||
@ -46,7 +49,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
if: success() && github.ref == 'refs/heads/master'
|
if: success() && github.ref == 'refs/heads/master'
|
||||||
uses: crazy-max/ghaction-github-pages@v2
|
uses: crazy-max/ghaction-github-pages@v3
|
||||||
with:
|
with:
|
||||||
target_branch: gh-pages
|
target_branch: gh-pages
|
||||||
build_dir: ./build/prod
|
build_dir: ./build/prod
|
||||||
|
27
.github/workflows/pull_requests.yml
vendored
27
.github/workflows/pull_requests.yml
vendored
@ -1,6 +1,7 @@
|
|||||||
name: "Pull Requests"
|
name: "Pull Requests"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [synchronize, opened, reopened]
|
types: [synchronize, opened, reopened]
|
||||||
|
|
||||||
@ -8,12 +9,12 @@ jobs:
|
|||||||
main:
|
main:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Set node version
|
- name: Set node version
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: '10.x'
|
node-version: '18.x'
|
||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
run: |
|
run: |
|
||||||
@ -26,12 +27,28 @@ jobs:
|
|||||||
- name: Unit Tests
|
- name: Unit Tests
|
||||||
run: |
|
run: |
|
||||||
npm test
|
npm test
|
||||||
npx grunt testnodeconsumer
|
npm run testnodeconsumer
|
||||||
|
|
||||||
- name: Production Build
|
- name: Production Build
|
||||||
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: xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
|
run: |
|
||||||
|
sudo apt-get install xvfb
|
||||||
|
xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
|
||||||
|
57
.github/workflows/releases.yml
vendored
57
.github/workflows/releases.yml
vendored
@ -1,24 +1,31 @@
|
|||||||
name: "Releases"
|
name: "Releases"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
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
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Set node version
|
- name: Set node version
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: '10.x'
|
node-version: '18.x'
|
||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
run: |
|
run: |
|
||||||
npm install
|
npm ci
|
||||||
npm run setheapsize
|
npm run setheapsize
|
||||||
|
|
||||||
- name: Lint
|
- name: Lint
|
||||||
@ -27,18 +34,41 @@ jobs:
|
|||||||
- name: Unit Tests
|
- name: Unit Tests
|
||||||
run: |
|
run: |
|
||||||
npm test
|
npm test
|
||||||
npx grunt 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: xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
|
sudo apt-get install xvfb
|
||||||
|
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:
|
||||||
@ -50,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 }}
|
||||||
|
3
.gitignore
vendored
3
.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
|
||||||
@ -11,4 +12,4 @@ src/node/config/OperationConfig.json
|
|||||||
src/node/index.mjs
|
src/node/index.mjs
|
||||||
**/*.DS_Store
|
**/*.DS_Store
|
||||||
tests/browser/output/*
|
tests/browser/output/*
|
||||||
|
.node-version
|
||||||
|
331
CHANGELOG.md
331
CHANGELOG.md
@ -13,6 +13,183 @@ All major and minor version changes will be documented in this file. Details of
|
|||||||
|
|
||||||
## Details
|
## Details
|
||||||
|
|
||||||
|
### [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
|
||||||
|
- Added GOST Encrypt, Decrypt, Sign, Verify, Key Wrap, and Key Unwrap operations [@n1474335] | [#592]
|
||||||
|
|
||||||
|
### [10.4.0] - 2023-03-24
|
||||||
|
- Added 'Generate De Bruijn Sequence' operation [@gchq77703] | [#493]
|
||||||
|
|
||||||
|
### [10.3.0] - 2023-03-24
|
||||||
|
- Added 'Argon2' and 'Argon2 compare' operations [@Xenonym] | [#661]
|
||||||
|
|
||||||
|
### [10.2.0] - 2023-03-23
|
||||||
|
- Added 'Derive HKDF key' operation [@mikecat] | [#1528]
|
||||||
|
|
||||||
|
### [10.1.0] - 2023-03-23
|
||||||
|
- Added 'Levenshtein Distance' operation [@mikecat] | [#1498]
|
||||||
|
- Added 'Swap case' operation [@mikecat] | [#1499]
|
||||||
|
|
||||||
|
## [10.0.0] - 2023-03-22
|
||||||
|
- [Full details explained here](https://github.com/gchq/CyberChef/wiki/Character-encoding,-EOL-separators,-and-editor-features)
|
||||||
|
- Status bars added to the Input and Output [@n1474335] | [#1405]
|
||||||
|
- Character encoding selection added to the Input and Output [@n1474335] | [#1405]
|
||||||
|
- End of line separator selection added to the Input and Output [@n1474335] | [#1405]
|
||||||
|
- Non-printable characters are rendered as control character pictures [@n1474335] | [#1405]
|
||||||
|
- Loaded files can now be edited in the Input [@n1474335] | [#1405]
|
||||||
|
- Various editor features added such as multiple selections and bracket matching [@n1474335] | [#1405]
|
||||||
|
- Contextual help added, activated by pressing F1 while hovering over features [@n1474335] | [#1405]
|
||||||
|
- Many, many UI tests added for I/O features and operations [@n1474335] | [#1405]
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click to expand v9 minor versions</summary>
|
||||||
|
|
||||||
|
### [9.55.0] - 2022-12-09
|
||||||
|
- Added 'AMF Encode' and 'AMF Decode' operations [@n1474335] | [760eff4]
|
||||||
|
|
||||||
|
### [9.54.0] - 2022-11-25
|
||||||
|
- Added 'Rabbit' operation [@mikecat] | [#1450]
|
||||||
|
|
||||||
|
### [9.53.0] - 2022-11-25
|
||||||
|
- Added 'AES Key Wrap' and 'AES Key Unwrap' operations [@mikecat] | [#1456]
|
||||||
|
|
||||||
|
### [9.52.0] - 2022-11-25
|
||||||
|
- Added 'ChaCha' operation [@joostrijneveld] | [#1466]
|
||||||
|
|
||||||
|
### [9.51.0] - 2022-11-25
|
||||||
|
- Added 'CMAC' operation [@mikecat] | [#1457]
|
||||||
|
|
||||||
|
### [9.50.0] - 2022-11-25
|
||||||
|
- Added 'Shuffle' operation [@mikecat] | [#1472]
|
||||||
|
|
||||||
|
### [9.49.0] - 2022-11-11
|
||||||
|
- Added 'LZ4 Compress' and 'LZ4 Decompress' operations [@n1474335] | [31a7f83]
|
||||||
|
|
||||||
|
### [9.48.0] - 2022-10-14
|
||||||
|
- Added 'LM Hash' and 'NT Hash' operations [@n1474335] [@brun0ne] | [#1427]
|
||||||
|
|
||||||
|
### [9.47.0] - 2022-10-14
|
||||||
|
- Added 'LZMA Decompress' and 'LZMA Compress' operations [@mattnotmitt] | [#1421]
|
||||||
|
|
||||||
|
### [9.46.0] - 2022-07-08
|
||||||
|
- Added 'Cetacean Cipher Encode' and 'Cetacean Cipher Decode' operations [@valdelaseras] | [#1308]
|
||||||
|
|
||||||
|
### [9.45.0] - 2022-07-08
|
||||||
|
- Added 'ROT8000' operation [@thomasleplus] | [#1250]
|
||||||
|
|
||||||
|
### [9.44.0] - 2022-07-08
|
||||||
|
- Added 'LZString Compress' and 'LZString Decompress' operations [@crespyl] | [#1266]
|
||||||
|
|
||||||
|
### [9.43.0] - 2022-07-08
|
||||||
|
- Added 'ROT13 Brute Force' and 'ROT47 Brute Force' operations [@mikecat] | [#1264]
|
||||||
|
|
||||||
|
### [9.42.0] - 2022-07-08
|
||||||
|
- Added 'LS47 Encrypt' and 'LS47 Decrypt' operations [@n1073645] | [#951]
|
||||||
|
|
||||||
|
### [9.41.0] - 2022-07-08
|
||||||
|
- Added 'Caesar Box Cipher' operation [@n1073645] | [#1066]
|
||||||
|
|
||||||
|
### [9.40.0] - 2022-07-08
|
||||||
|
- Added 'P-list Viewer' operation [@n1073645] | [#906]
|
||||||
|
|
||||||
|
### [9.39.0] - 2022-06-09
|
||||||
|
- Added 'ELF Info' operation [@n1073645] | [#1364]
|
||||||
|
|
||||||
|
### [9.38.0] - 2022-05-30
|
||||||
|
- Added 'Parse TCP' operation [@n1474335] | [a895d1d]
|
||||||
|
|
||||||
|
### [9.37.0] - 2022-03-29
|
||||||
|
- 'SM4 Encrypt' and 'SM4 Decrypt' operations added [@swesven] | [#1189]
|
||||||
|
- NoPadding options added for CBC and ECB modes in AES, DES and Triple DES Decrypt operations [@swesven] | [#1189]
|
||||||
|
|
||||||
|
### [9.36.0] - 2022-03-29
|
||||||
|
- 'SIGABA' operation added [@hettysymes] | [#934]
|
||||||
|
|
||||||
|
### [9.35.0] - 2022-03-28
|
||||||
|
- 'To Base45' and 'From Base45' operations added [@t-8ch] | [#1242]
|
||||||
|
|
||||||
|
### [9.34.0] - 2022-03-28
|
||||||
|
- 'Get All Casings' operation added [@n1073645] | [#1065]
|
||||||
|
|
||||||
|
### [9.33.0] - 2022-03-25
|
||||||
|
- Updated to support Node 17 [@n1474335] [@john19696] [@t-8ch] | [[#1326] [#1313] [#1244]
|
||||||
|
- Improved CJS and ESM module support [@d98762625] | [#1037]
|
||||||
|
|
||||||
|
### [9.32.0] - 2021-08-18
|
||||||
|
- 'Protobuf Encode' operation added and decode operation modified to allow decoding with full and partial schemas [@n1474335] | [dd18e52]
|
||||||
|
|
||||||
|
### [9.31.0] - 2021-08-10
|
||||||
|
- 'HASSH Client Fingerprint' and 'HASSH Server Fingerprint' operations added [@n1474335] | [e9ca4dc]
|
||||||
|
|
||||||
|
### [9.30.0] - 2021-08-10
|
||||||
|
- 'JA3S Fingerprint' operation added [@n1474335] | [289a417]
|
||||||
|
|
||||||
|
### [9.29.0] - 2021-07-28
|
||||||
|
- 'JA3 Fingerprint' operation added [@n1474335] | [9a33498]
|
||||||
|
|
||||||
### [9.28.0] - 2021-03-26
|
### [9.28.0] - 2021-03-26
|
||||||
- 'CBOR Encode' and 'CBOR Decode' operations added [@Danh4] | [#999]
|
- 'CBOR Encode' and 'CBOR Decode' operations added [@Danh4] | [#999]
|
||||||
|
|
||||||
@ -98,6 +275,8 @@ All major and minor version changes will be documented in this file. Details of
|
|||||||
- 'Parse SSH Host Key' operation added [@j433866] | [#595]
|
- 'Parse SSH Host Key' operation added [@j433866] | [#595]
|
||||||
- 'Defang IP Addresses' operation added [@h345983745] | [#556]
|
- 'Defang IP Addresses' operation added [@h345983745] | [#556]
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## [9.0.0] - 2019-07-09
|
## [9.0.0] - 2019-07-09
|
||||||
- [Multiple inputs](https://github.com/gchq/CyberChef/wiki/Multiple-Inputs) are now supported in the main web UI, allowing you to upload and process multiple files at once [@j433866] | [#566]
|
- [Multiple inputs](https://github.com/gchq/CyberChef/wiki/Multiple-Inputs) are now supported in the main web UI, allowing you to upload and process multiple files at once [@j433866] | [#566]
|
||||||
- A [Node.js API](https://github.com/gchq/CyberChef/wiki/Node-API) has been implemented, meaning that CyberChef can now be used as a library, either to provide specific operations, or an entire baking environment [@d98762625] | [#291]
|
- A [Node.js API](https://github.com/gchq/CyberChef/wiki/Node-API) has been implemented, meaning that CyberChef can now be used as a library, either to provide specific operations, or an entire baking environment [@d98762625] | [#291]
|
||||||
@ -106,7 +285,7 @@ All major and minor version changes will be documented in this file. Details of
|
|||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Click to expand v8 minor versions</summary>
|
<summary>Click to expand v8 minor versions</summary>
|
||||||
|
|
||||||
### [8.38.0] - 2019-07-03
|
### [8.38.0] - 2019-07-03
|
||||||
- 'Streebog' and 'GOST hash' operations added [@MShwed] [@n1474335] | [#530]
|
- 'Streebog' and 'GOST hash' operations added [@MShwed] [@n1474335] | [#530]
|
||||||
|
|
||||||
@ -257,8 +436,52 @@ 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.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.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.2.0]: https://github.com/gchq/CyberChef/releases/tag/v10.2.0
|
||||||
|
[10.1.0]: https://github.com/gchq/CyberChef/releases/tag/v10.1.0
|
||||||
|
[10.0.0]: https://github.com/gchq/CyberChef/releases/tag/v10.0.0
|
||||||
|
[9.55.0]: https://github.com/gchq/CyberChef/releases/tag/v9.55.0
|
||||||
|
[9.54.0]: https://github.com/gchq/CyberChef/releases/tag/v9.54.0
|
||||||
|
[9.53.0]: https://github.com/gchq/CyberChef/releases/tag/v9.53.0
|
||||||
|
[9.52.0]: https://github.com/gchq/CyberChef/releases/tag/v9.52.0
|
||||||
|
[9.51.0]: https://github.com/gchq/CyberChef/releases/tag/v9.51.0
|
||||||
|
[9.50.0]: https://github.com/gchq/CyberChef/releases/tag/v9.50.0
|
||||||
|
[9.49.0]: https://github.com/gchq/CyberChef/releases/tag/v9.49.0
|
||||||
|
[9.48.0]: https://github.com/gchq/CyberChef/releases/tag/v9.48.0
|
||||||
|
[9.47.0]: https://github.com/gchq/CyberChef/releases/tag/v9.47.0
|
||||||
|
[9.46.0]: https://github.com/gchq/CyberChef/releases/tag/v9.46.0
|
||||||
|
[9.45.0]: https://github.com/gchq/CyberChef/releases/tag/v9.45.0
|
||||||
|
[9.44.0]: https://github.com/gchq/CyberChef/releases/tag/v9.44.0
|
||||||
|
[9.43.0]: https://github.com/gchq/CyberChef/releases/tag/v9.43.0
|
||||||
|
[9.42.0]: https://github.com/gchq/CyberChef/releases/tag/v9.42.0
|
||||||
|
[9.41.0]: https://github.com/gchq/CyberChef/releases/tag/v9.41.0
|
||||||
|
[9.40.0]: https://github.com/gchq/CyberChef/releases/tag/v9.40.0
|
||||||
|
[9.39.0]: https://github.com/gchq/CyberChef/releases/tag/v9.39.0
|
||||||
|
[9.38.0]: https://github.com/gchq/CyberChef/releases/tag/v9.38.0
|
||||||
|
[9.37.0]: https://github.com/gchq/CyberChef/releases/tag/v9.37.0
|
||||||
|
[9.36.0]: https://github.com/gchq/CyberChef/releases/tag/v9.36.0
|
||||||
|
[9.35.0]: https://github.com/gchq/CyberChef/releases/tag/v9.35.0
|
||||||
|
[9.34.0]: https://github.com/gchq/CyberChef/releases/tag/v9.34.0
|
||||||
|
[9.33.0]: https://github.com/gchq/CyberChef/releases/tag/v9.33.0
|
||||||
|
[9.32.0]: https://github.com/gchq/CyberChef/releases/tag/v9.32.0
|
||||||
|
[9.31.0]: https://github.com/gchq/CyberChef/releases/tag/v9.31.0
|
||||||
|
[9.30.0]: https://github.com/gchq/CyberChef/releases/tag/v9.30.0
|
||||||
|
[9.29.0]: https://github.com/gchq/CyberChef/releases/tag/v9.29.0
|
||||||
[9.28.0]: https://github.com/gchq/CyberChef/releases/tag/v9.28.0
|
[9.28.0]: https://github.com/gchq/CyberChef/releases/tag/v9.28.0
|
||||||
[9.27.0]: https://github.com/gchq/CyberChef/releases/tag/v9.27.0
|
[9.27.0]: https://github.com/gchq/CyberChef/releases/tag/v9.27.0
|
||||||
[9.26.0]: https://github.com/gchq/CyberChef/releases/tag/v9.26.0
|
[9.26.0]: https://github.com/gchq/CyberChef/releases/tag/v9.26.0
|
||||||
@ -368,8 +591,50 @@ All major and minor version changes will be documented in this file. Details of
|
|||||||
[@dmfj]: https://github.com/dmfj
|
[@dmfj]: https://github.com/dmfj
|
||||||
[@mattnotmitt]: https://github.com/mattnotmitt
|
[@mattnotmitt]: https://github.com/mattnotmitt
|
||||||
[@Danh4]: https://github.com/Danh4
|
[@Danh4]: https://github.com/Danh4
|
||||||
|
[@john19696]: https://github.com/john19696
|
||||||
|
[@t-8ch]: https://github.com/t-8ch
|
||||||
|
[@hettysymes]: https://github.com/hettysymes
|
||||||
|
[@swesven]: https://github.com/swesven
|
||||||
|
[@mikecat]: https://github.com/mikecat
|
||||||
|
[@crespyl]: https://github.com/crespyl
|
||||||
|
[@thomasleplus]: https://github.com/thomasleplus
|
||||||
|
[@valdelaseras]: https://github.com/valdelaseras
|
||||||
|
[@brun0ne]: https://github.com/brun0ne
|
||||||
|
[@joostrijneveld]: https://github.com/joostrijneveld
|
||||||
|
[@Xenonym]: https://github.com/Xenonym
|
||||||
|
[@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
|
||||||
|
|
||||||
|
|
||||||
[8ad18b]: https://github.com/gchq/CyberChef/commit/8ad18bc7db6d9ff184ba3518686293a7685bf7b7
|
[8ad18b]: https://github.com/gchq/CyberChef/commit/8ad18bc7db6d9ff184ba3518686293a7685bf7b7
|
||||||
|
[9a33498]: https://github.com/gchq/CyberChef/commit/9a33498fed26a8df9c9f35f39a78a174bf50a513
|
||||||
|
[289a417]: https://github.com/gchq/CyberChef/commit/289a417dfb5923de5e1694354ec42a08d9395bfe
|
||||||
|
[e9ca4dc]: https://github.com/gchq/CyberChef/commit/e9ca4dc9caf98f33fd986431cd400c88082a42b8
|
||||||
|
[dd18e52]: https://github.com/gchq/CyberChef/commit/dd18e529939078b89867297b181a584e8b2cc7da
|
||||||
|
[a895d1d]: https://github.com/gchq/CyberChef/commit/a895d1d82a2f92d440a0c5eca2bc7c898107b737
|
||||||
|
[31a7f83]: https://github.com/gchq/CyberChef/commit/31a7f83b82e78927f89689f323fcb9185144d6ff
|
||||||
|
[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
|
||||||
@ -436,9 +701,12 @@ All major and minor version changes will be documented in this file. Details of
|
|||||||
[#674]: https://github.com/gchq/CyberChef/pull/674
|
[#674]: https://github.com/gchq/CyberChef/pull/674
|
||||||
[#683]: https://github.com/gchq/CyberChef/pull/683
|
[#683]: https://github.com/gchq/CyberChef/pull/683
|
||||||
[#865]: https://github.com/gchq/CyberChef/pull/865
|
[#865]: https://github.com/gchq/CyberChef/pull/865
|
||||||
|
[#906]: https://github.com/gchq/CyberChef/pull/906
|
||||||
[#912]: https://github.com/gchq/CyberChef/pull/912
|
[#912]: https://github.com/gchq/CyberChef/pull/912
|
||||||
[#917]: https://github.com/gchq/CyberChef/pull/917
|
[#917]: https://github.com/gchq/CyberChef/pull/917
|
||||||
|
[#934]: https://github.com/gchq/CyberChef/pull/934
|
||||||
[#948]: https://github.com/gchq/CyberChef/pull/948
|
[#948]: https://github.com/gchq/CyberChef/pull/948
|
||||||
|
[#951]: https://github.com/gchq/CyberChef/pull/951
|
||||||
[#952]: https://github.com/gchq/CyberChef/pull/952
|
[#952]: https://github.com/gchq/CyberChef/pull/952
|
||||||
[#965]: https://github.com/gchq/CyberChef/pull/965
|
[#965]: https://github.com/gchq/CyberChef/pull/965
|
||||||
[#966]: https://github.com/gchq/CyberChef/pull/966
|
[#966]: https://github.com/gchq/CyberChef/pull/966
|
||||||
@ -446,6 +714,61 @@ All major and minor version changes will be documented in this file. Details of
|
|||||||
[#999]: https://github.com/gchq/CyberChef/pull/999
|
[#999]: https://github.com/gchq/CyberChef/pull/999
|
||||||
[#1006]: https://github.com/gchq/CyberChef/pull/1006
|
[#1006]: https://github.com/gchq/CyberChef/pull/1006
|
||||||
[#1022]: https://github.com/gchq/CyberChef/pull/1022
|
[#1022]: https://github.com/gchq/CyberChef/pull/1022
|
||||||
|
[#1037]: https://github.com/gchq/CyberChef/pull/1037
|
||||||
[#1045]: https://github.com/gchq/CyberChef/pull/1045
|
[#1045]: https://github.com/gchq/CyberChef/pull/1045
|
||||||
[#1049]: https://github.com/gchq/CyberChef/pull/1049
|
[#1049]: https://github.com/gchq/CyberChef/pull/1049
|
||||||
[#1083]: https://github.com/gchq/CyberChef/pull/1083
|
[#1065]: https://github.com/gchq/CyberChef/pull/1065
|
||||||
|
[#1066]: https://github.com/gchq/CyberChef/pull/1066
|
||||||
|
[#1083]: https://github.com/gchq/CyberChef/pull/1083
|
||||||
|
[#1189]: https://github.com/gchq/CyberChef/pull/1189
|
||||||
|
[#1242]: https://github.com/gchq/CyberChef/pull/1242
|
||||||
|
[#1244]: https://github.com/gchq/CyberChef/pull/1244
|
||||||
|
[#1313]: https://github.com/gchq/CyberChef/pull/1313
|
||||||
|
[#1326]: https://github.com/gchq/CyberChef/pull/1326
|
||||||
|
[#1364]: https://github.com/gchq/CyberChef/pull/1364
|
||||||
|
[#1264]: https://github.com/gchq/CyberChef/pull/1264
|
||||||
|
[#1266]: https://github.com/gchq/CyberChef/pull/1266
|
||||||
|
[#1250]: https://github.com/gchq/CyberChef/pull/1250
|
||||||
|
[#1308]: https://github.com/gchq/CyberChef/pull/1308
|
||||||
|
[#1405]: https://github.com/gchq/CyberChef/pull/1405
|
||||||
|
[#1421]: https://github.com/gchq/CyberChef/pull/1421
|
||||||
|
[#1427]: https://github.com/gchq/CyberChef/pull/1427
|
||||||
|
[#1472]: https://github.com/gchq/CyberChef/pull/1472
|
||||||
|
[#1457]: https://github.com/gchq/CyberChef/pull/1457
|
||||||
|
[#1466]: https://github.com/gchq/CyberChef/pull/1466
|
||||||
|
[#1456]: https://github.com/gchq/CyberChef/pull/1456
|
||||||
|
[#1450]: https://github.com/gchq/CyberChef/pull/1450
|
||||||
|
[#1498]: https://github.com/gchq/CyberChef/pull/1498
|
||||||
|
[#1499]: https://github.com/gchq/CyberChef/pull/1499
|
||||||
|
[#1528]: https://github.com/gchq/CyberChef/pull/1528
|
||||||
|
[#661]: https://github.com/gchq/CyberChef/pull/661
|
||||||
|
[#493]: https://github.com/gchq/CyberChef/pull/493
|
||||||
|
[#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/
|
159
Gruntfile.js
159
Gruntfile.js
@ -6,6 +6,8 @@ const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPl
|
|||||||
const glob = require("glob");
|
const glob = require("glob");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
|
const nodeFlags = "--experimental-modules --experimental-json-modules --experimental-specifier-resolution=node --no-warnings --no-deprecation";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Grunt configuration for building the app in various formats.
|
* Grunt configuration for building the app in various formats.
|
||||||
*
|
*
|
||||||
@ -27,7 +29,7 @@ module.exports = function (grunt) {
|
|||||||
"Creates a production-ready build. Use the --msg flag to add a compile message.",
|
"Creates a production-ready build. Use the --msg flag to add a compile message.",
|
||||||
[
|
[
|
||||||
"eslint", "clean:prod", "clean:config", "exec:generateConfig", "findModules", "webpack:web",
|
"eslint", "clean:prod", "clean:config", "exec:generateConfig", "findModules", "webpack:web",
|
||||||
"copy:standalone", "zip:standalone", "clean:standalone", "chmod"
|
"copy:standalone", "zip:standalone", "clean:standalone", "exec:calcDownloadHash", "chmod"
|
||||||
]);
|
]);
|
||||||
|
|
||||||
grunt.registerTask("node",
|
grunt.registerTask("node",
|
||||||
@ -48,7 +50,7 @@ module.exports = function (grunt) {
|
|||||||
|
|
||||||
grunt.registerTask("testnodeconsumer",
|
grunt.registerTask("testnodeconsumer",
|
||||||
"A task which checks whether consuming CJS and ESM apps work with the CyberChef build",
|
"A task which checks whether consuming CJS and ESM apps work with the CyberChef build",
|
||||||
["exec:setupNodeConsumers", "exec:testCJSNodeConsumer", "exec:testESMNodeConsumer", "exec:testESMDeepImportNodeConsumer", "exec:teardownNodeConsumers"]);
|
["exec:setupNodeConsumers", "exec:testCJSNodeConsumer", "exec:testESMNodeConsumer", "exec:teardownNodeConsumers"]);
|
||||||
|
|
||||||
grunt.registerTask("default",
|
grunt.registerTask("default",
|
||||||
"Lints the code base",
|
"Lints the code base",
|
||||||
@ -84,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),
|
||||||
@ -123,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: {
|
||||||
@ -187,9 +192,6 @@ module.exports = function (grunt) {
|
|||||||
standalone: ["build/prod/CyberChef*.html"]
|
standalone: ["build/prod/CyberChef*.html"]
|
||||||
},
|
},
|
||||||
eslint: {
|
eslint: {
|
||||||
options: {
|
|
||||||
configFile: "./.eslintrc.json"
|
|
||||||
},
|
|
||||||
configs: ["*.{js,mjs}"],
|
configs: ["*.{js,mjs}"],
|
||||||
core: ["src/core/**/*.{js,mjs}", "!src/core/vendor/**/*", "!src/core/operations/legacy/**/*"],
|
core: ["src/core/**/*.{js,mjs}", "!src/core/vendor/**/*", "!src/core/operations/legacy/**/*"],
|
||||||
web: ["src/web/**/*.{js,mjs}", "!src/web/static/**/*"],
|
web: ["src/web/**/*.{js,mjs}", "!src/web/static/**/*"],
|
||||||
@ -198,53 +200,41 @@ module.exports = function (grunt) {
|
|||||||
},
|
},
|
||||||
webpack: {
|
webpack: {
|
||||||
options: webpackConfig,
|
options: webpackConfig,
|
||||||
|
myConfig: webpackConfig,
|
||||||
web: webpackProdConf(),
|
web: webpackProdConf(),
|
||||||
},
|
},
|
||||||
"webpack-dev-server": {
|
"webpack-dev-server": {
|
||||||
options: {
|
options: webpackConfig,
|
||||||
webpack: webpackConfig,
|
|
||||||
host: "0.0.0.0",
|
|
||||||
port: grunt.option("port") || 8080,
|
|
||||||
disableHostCheck: true,
|
|
||||||
overlay: true,
|
|
||||||
inline: false,
|
|
||||||
clientLogLevel: "error",
|
|
||||||
stats: {
|
|
||||||
children: false,
|
|
||||||
chunks: false,
|
|
||||||
modules: false,
|
|
||||||
entrypoints: false,
|
|
||||||
warningsFilter: [
|
|
||||||
/source-map/,
|
|
||||||
/dependency is an expression/,
|
|
||||||
/export 'default'/,
|
|
||||||
/Can't resolve 'sodium'/
|
|
||||||
],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
start: {
|
start: {
|
||||||
webpack: {
|
mode: "development",
|
||||||
mode: "development",
|
target: "web",
|
||||||
target: "web",
|
entry: Object.assign({
|
||||||
entry: Object.assign({
|
main: "./src/web/index.js"
|
||||||
main: "./src/web/index.js"
|
}, moduleEntryPoints),
|
||||||
}, moduleEntryPoints),
|
resolve: {
|
||||||
resolve: {
|
alias: {
|
||||||
alias: {
|
"./config/modules/OpModules.mjs": "./config/modules/Default.mjs"
|
||||||
"./config/modules/OpModules.mjs": "./config/modules/Default.mjs"
|
}
|
||||||
}
|
},
|
||||||
|
devServer: {
|
||||||
|
port: grunt.option("port") || 8080,
|
||||||
|
client: {
|
||||||
|
logging: "error",
|
||||||
|
overlay: true
|
||||||
},
|
},
|
||||||
plugins: [
|
hot: "only"
|
||||||
new webpack.DefinePlugin(BUILD_CONSTANTS),
|
},
|
||||||
new HtmlWebpackPlugin({
|
plugins: [
|
||||||
filename: "index.html",
|
new webpack.DefinePlugin(BUILD_CONSTANTS),
|
||||||
template: "./src/web/html/index.html",
|
new HtmlWebpackPlugin({
|
||||||
chunks: ["main"],
|
filename: "index.html",
|
||||||
compileTime: compileTime,
|
template: "./src/web/html/index.html",
|
||||||
version: pkg.version,
|
chunks: ["main"],
|
||||||
})
|
compileYear: compileYear,
|
||||||
]
|
compileTime: compileTime,
|
||||||
}
|
version: pkg.version,
|
||||||
|
})
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
zip: {
|
zip: {
|
||||||
@ -338,6 +328,22 @@ module.exports = function (grunt) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
exec: {
|
exec: {
|
||||||
|
calcDownloadHash: {
|
||||||
|
command: function () {
|
||||||
|
switch (process.platform) {
|
||||||
|
case "darwin":
|
||||||
|
return chainCommands([
|
||||||
|
`shasum -a 256 build/prod/CyberChef_v${pkg.version}.zip | awk '{print $1;}' > build/prod/sha256digest.txt`,
|
||||||
|
`sed -i '' -e "s/DOWNLOAD_HASH_PLACEHOLDER/$(cat build/prod/sha256digest.txt)/" build/prod/index.html`
|
||||||
|
]);
|
||||||
|
default:
|
||||||
|
return chainCommands([
|
||||||
|
`sha256sum build/prod/CyberChef_v${pkg.version}.zip | awk '{print $1;}' > build/prod/sha256digest.txt`,
|
||||||
|
`sed -i -e "s/DOWNLOAD_HASH_PLACEHOLDER/$(cat build/prod/sha256digest.txt)/" build/prod/index.html`
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
repoSize: {
|
repoSize: {
|
||||||
command: chainCommands([
|
command: chainCommands([
|
||||||
"git ls-files | wc -l | xargs printf '\n%b\ttracked files\n'",
|
"git ls-files | wc -l | xargs printf '\n%b\ttracked files\n'",
|
||||||
@ -349,15 +355,15 @@ module.exports = function (grunt) {
|
|||||||
command: "git gc --prune=now --aggressive"
|
command: "git gc --prune=now --aggressive"
|
||||||
},
|
},
|
||||||
sitemap: {
|
sitemap: {
|
||||||
command: "node --experimental-modules --no-warnings --no-deprecation src/web/static/sitemap.mjs > build/prod/sitemap.xml",
|
command: `node ${nodeFlags} src/web/static/sitemap.mjs > build/prod/sitemap.xml`,
|
||||||
sync: true
|
sync: true
|
||||||
},
|
},
|
||||||
generateConfig: {
|
generateConfig: {
|
||||||
command: chainCommands([
|
command: chainCommands([
|
||||||
"echo '\n--- Regenerating config files. ---'",
|
"echo '\n--- Regenerating config files. ---'",
|
||||||
"echo [] > src/core/config/OperationConfig.json",
|
"echo [] > src/core/config/OperationConfig.json",
|
||||||
"node --experimental-modules --no-warnings --no-deprecation src/core/config/scripts/generateOpsIndex.mjs",
|
`node ${nodeFlags} src/core/config/scripts/generateOpsIndex.mjs`,
|
||||||
"node --experimental-modules --no-warnings --no-deprecation src/core/config/scripts/generateConfig.mjs",
|
`node ${nodeFlags} src/core/config/scripts/generateConfig.mjs`,
|
||||||
"echo '--- Config scripts finished. ---\n'"
|
"echo '--- Config scripts finished. ---\n'"
|
||||||
]),
|
]),
|
||||||
sync: true
|
sync: true
|
||||||
@ -365,7 +371,7 @@ module.exports = function (grunt) {
|
|||||||
generateNodeIndex: {
|
generateNodeIndex: {
|
||||||
command: chainCommands([
|
command: chainCommands([
|
||||||
"echo '\n--- Regenerating node index ---'",
|
"echo '\n--- Regenerating node index ---'",
|
||||||
"node --experimental-modules --no-warnings --no-deprecation src/node/config/scripts/generateNodeIndex.mjs",
|
`node ${nodeFlags} src/node/config/scripts/generateNodeIndex.mjs`,
|
||||||
"echo '--- Node index generated. ---\n'"
|
"echo '--- Node index generated. ---\n'"
|
||||||
]),
|
]),
|
||||||
sync: true
|
sync: true
|
||||||
@ -393,32 +399,49 @@ module.exports = function (grunt) {
|
|||||||
testCJSNodeConsumer: {
|
testCJSNodeConsumer: {
|
||||||
command: chainCommands([
|
command: chainCommands([
|
||||||
`cd ${nodeConsumerTestPath}`,
|
`cd ${nodeConsumerTestPath}`,
|
||||||
"node --no-warnings cjs-consumer.js",
|
`node ${nodeFlags} cjs-consumer.js`,
|
||||||
]),
|
]),
|
||||||
stdout: false,
|
stdout: false,
|
||||||
},
|
},
|
||||||
testESMNodeConsumer: {
|
testESMNodeConsumer: {
|
||||||
command: chainCommands([
|
command: chainCommands([
|
||||||
`cd ${nodeConsumerTestPath}`,
|
`cd ${nodeConsumerTestPath}`,
|
||||||
"node --no-warnings --experimental-modules esm-consumer.mjs",
|
`node ${nodeFlags} esm-consumer.mjs`,
|
||||||
]),
|
|
||||||
stdout: false,
|
|
||||||
},
|
|
||||||
testESMDeepImportNodeConsumer: {
|
|
||||||
command: chainCommands([
|
|
||||||
`cd ${nodeConsumerTestPath}`,
|
|
||||||
"node --no-warnings --experimental-modules esm-deep-import-consumer.mjs",
|
|
||||||
]),
|
]),
|
||||||
stdout: false,
|
stdout: false,
|
||||||
},
|
},
|
||||||
fixCryptoApiImports: {
|
fixCryptoApiImports: {
|
||||||
command: [
|
command: function () {
|
||||||
`[[ "$OSTYPE" == "darwin"* ]]`,
|
switch (process.platform) {
|
||||||
"&&",
|
case "darwin":
|
||||||
`find ./node_modules/crypto-api/src/ \\( -type d -name .git -prune \\) -o -type f -print0 | xargs -0 sed -i '' -e '/\\.mjs/!s/\\(from "\\.[^"]*\\)";/\\1.mjs";/g'`,
|
return `find ./node_modules/crypto-api/src/ \\( -type d -name .git -prune \\) -o -type f -print0 | xargs -0 sed -i '' -e '/\\.mjs/!s/\\(from "\\.[^"]*\\)";/\\1.mjs";/g'`;
|
||||||
"||",
|
default:
|
||||||
`find ./node_modules/crypto-api/src/ \\( -type d -name .git -prune \\) -o -type f -print0 | xargs -0 sed -i -e '/\\.mjs/!s/\\(from "\\.[^"]*\\)";/\\1.mjs";/g'`
|
return `find ./node_modules/crypto-api/src/ \\( -type d -name .git -prune \\) -o -type f -print0 | xargs -0 sed -i -e '/\\.mjs/!s/\\(from "\\.[^"]*\\)";/\\1.mjs";/g'`;
|
||||||
].join(" "),
|
}
|
||||||
|
},
|
||||||
|
stdout: false
|
||||||
|
},
|
||||||
|
fixSnackbarMarkup: {
|
||||||
|
command: function () {
|
||||||
|
switch (process.platform) {
|
||||||
|
case "darwin":
|
||||||
|
return `sed -i '' 's/<div id=snackbar-container\\/>/<div id=snackbar-container>/g' ./node_modules/snackbarjs/src/snackbar.js`;
|
||||||
|
default:
|
||||||
|
return `sed -i 's/<div id=snackbar-container\\/>/<div id=snackbar-container>/g' ./node_modules/snackbarjs/src/snackbar.js`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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
|
stdout: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
36
README.md
36
README.md
@ -1,8 +1,6 @@
|
|||||||
# CyberChef
|
# CyberChef
|
||||||
|
|
||||||
[![](https://github.com/gchq/CyberChef/workflows/Master%20Build,%20Test%20&%20Deploy/badge.svg)](https://github.com/gchq/CyberChef/actions?query=workflow%3A%22Master+Build%2C+Test+%26+Deploy%22)
|
[![](https://github.com/gchq/CyberChef/workflows/Master%20Build,%20Test%20&%20Deploy/badge.svg)](https://github.com/gchq/CyberChef/actions?query=workflow%3A%22Master+Build%2C+Test+%26+Deploy%22)
|
||||||
[![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/gchq/CyberChef.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/gchq/CyberChef/context:javascript)
|
|
||||||
[![dependencies Status](https://david-dm.org/gchq/CyberChef/status.svg)](https://david-dm.org/gchq/CyberChef)
|
|
||||||
[![npm](https://img.shields.io/npm/v/cyberchef.svg)](https://www.npmjs.com/package/cyberchef)
|
[![npm](https://img.shields.io/npm/v/cyberchef.svg)](https://www.npmjs.com/package/cyberchef)
|
||||||
[![](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/gchq/CyberChef/blob/master/LICENSE)
|
[![](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/gchq/CyberChef/blob/master/LICENSE)
|
||||||
[![Gitter](https://badges.gitter.im/gchq/CyberChef.svg)](https://gitter.im/gchq/CyberChef?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
[![Gitter](https://badges.gitter.im/gchq/CyberChef.svg)](https://gitter.im/gchq/CyberChef?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||||
@ -10,7 +8,7 @@
|
|||||||
|
|
||||||
#### *The Cyber Swiss Army Knife*
|
#### *The Cyber Swiss Army Knife*
|
||||||
|
|
||||||
CyberChef is a simple, intuitive web app for carrying out all manner of "cyber" operations within a web browser. These operations include simple encoding like XOR or Base64, more complex encryption like AES, DES and Blowfish, creating binary and hexdumps, compression and decompression of data, calculating hashes and checksums, IPv6 and X.509 parsing, changing character encodings, and much more.
|
CyberChef is a simple, intuitive web app for carrying out all manner of "cyber" operations within a web browser. These operations include simple encoding like XOR and Base64, more complex encryption like AES, DES and Blowfish, creating binary and hexdumps, compression and decompression of data, calculating hashes and checksums, IPv6 and X.509 parsing, changing character encodings, and much more.
|
||||||
|
|
||||||
The tool is designed to enable both technical and non-technical analysts to manipulate data in complex ways without having to deal with complex tools or algorithms. It was conceived, designed, built and incrementally improved by an analyst in their 10% innovation time over several years.
|
The tool is designed to enable both technical and non-technical analysts to manipulate data in complex ways without having to deal with complex tools or algorithms. It was conceived, designed, built and incrementally improved by an analyst in their 10% innovation time over several years.
|
||||||
|
|
||||||
@ -22,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
|
||||||
|
|
||||||
@ -55,7 +69,7 @@ You can use as many operations as you like in simple or complex ways. Some examp
|
|||||||
- Whenever you modify the input or the recipe, CyberChef will automatically "bake" for you and produce the output immediately.
|
- Whenever you modify the input or the recipe, CyberChef will automatically "bake" for you and produce the output immediately.
|
||||||
- This can be turned off and operated manually if it is affecting performance (if the input is very large, for instance).
|
- This can be turned off and operated manually if it is affecting performance (if the input is very large, for instance).
|
||||||
- Automated encoding detection
|
- Automated encoding detection
|
||||||
- CyberChef uses [a number of techniques](https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic) to attempt to automatically detect which encodings your data is under. If it finds a suitable operation which can make sense of your data, it displays the 'magic' icon in the Output field which you can click to decode your data.
|
- CyberChef uses [a number of techniques](https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic) to attempt to automatically detect which encodings your data is under. If it finds a suitable operation that make sense of your data, it displays the 'magic' icon in the Output field which you can click to decode your data.
|
||||||
- Breakpoints
|
- Breakpoints
|
||||||
- You can set breakpoints on any operation in your recipe to pause execution before running it.
|
- You can set breakpoints on any operation in your recipe to pause execution before running it.
|
||||||
- You can also step through the recipe one operation at a time to see what the data looks like at each stage.
|
- You can also step through the recipe one operation at a time to see what the data looks like at each stage.
|
||||||
@ -67,7 +81,7 @@ You can use as many operations as you like in simple or complex ways. Some examp
|
|||||||
- Highlighting
|
- Highlighting
|
||||||
- When you highlight text in the input or output, the offset and length values will be displayed and, if possible, the corresponding data will be highlighted in the output or input respectively (example: [highlight the word 'question' in the input to see where it appears in the output][11]).
|
- When you highlight text in the input or output, the offset and length values will be displayed and, if possible, the corresponding data will be highlighted in the output or input respectively (example: [highlight the word 'question' in the input to see where it appears in the output][11]).
|
||||||
- Save to file and load from file
|
- Save to file and load from file
|
||||||
- You can save the output to a file at any time or load a file by dragging and dropping it into the input field. Files up to around 2GB are supported (depending on your browser), however some operations may take a very long time to run over this much data.
|
- You can save the output to a file at any time or load a file by dragging and dropping it into the input field. Files up to around 2GB are supported (depending on your browser), however, some operations may take a very long time to run over this much data.
|
||||||
- CyberChef is entirely client-side
|
- CyberChef is entirely client-side
|
||||||
- It should be noted that none of your recipe configuration or input (either text or files) is ever sent to the CyberChef web server - all processing is carried out within your browser, on your own computer.
|
- It should be noted that none of your recipe configuration or input (either text or files) is ever sent to the CyberChef web server - all processing is carried out within your browser, on your own computer.
|
||||||
- Due to this feature, CyberChef can be downloaded and run locally. You can use the link in the top left corner of the app to download a full copy of CyberChef and drop it into a virtual machine, share it with other people, or host it in a closed network.
|
- Due to this feature, CyberChef can be downloaded and run locally. You can use the link in the top left corner of the app to download a full copy of CyberChef and drop it into a virtual machine, share it with other people, or host it in a closed network.
|
||||||
@ -75,10 +89,10 @@ You can use as many operations as you like in simple or complex ways. Some examp
|
|||||||
|
|
||||||
## Deep linking
|
## Deep linking
|
||||||
|
|
||||||
By manipulation of CyberChef's URL hash, you can change the initial settings with which the page opens.
|
By manipulating CyberChef's URL hash, you can change the initial settings with which the page opens.
|
||||||
The format is `https://gchq.github.io/CyberChef/#recipe=Operation()&input=...`
|
The format is `https://gchq.github.io/CyberChef/#recipe=Operation()&input=...`
|
||||||
|
|
||||||
Supported arguments are `recipe`, `input` (encoded in Base64), and `theme`.
|
Supported arguments are `recipe`, `input` (encoded in Base64), and `theme`.
|
||||||
|
|
||||||
|
|
||||||
## Browser support
|
## Browser support
|
||||||
@ -91,14 +105,14 @@ CyberChef is built to support
|
|||||||
|
|
||||||
## Node.js support
|
## Node.js support
|
||||||
|
|
||||||
CyberChef is built to fully support Node.js `v10` and partially supports `v12`. Named imports using a deep import specifier does not work in `v12`. For more information, see the Node API page in the project [wiki pages](https://github.com/gchq/CyberChef/wiki)
|
CyberChef is built to fully support Node.js `v16`. For more information, see the ["Node API" wiki page](https://github.com/gchq/CyberChef/wiki/Node-API)
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Contributing a new operation to CyberChef is super easy! There is a quickstart script which will walk you through the process. If you can write basic JavaScript, you can write a CyberChef operation.
|
Contributing a new operation to CyberChef is super easy! The quickstart script will walk you through the process. If you can write basic JavaScript, you can write a CyberChef operation.
|
||||||
|
|
||||||
An installation walkthrough, how-to guides for adding new operations and themes, descriptions of the repository structure, available data types and coding conventions can all be found in the project [wiki pages](https://github.com/gchq/CyberChef/wiki).
|
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.
|
||||||
@ -106,7 +120,7 @@ An installation walkthrough, how-to guides for adding new operations and themes,
|
|||||||
|
|
||||||
## Licencing
|
## Licencing
|
||||||
|
|
||||||
CyberChef is released under the [Apache 2.0 Licence](https://www.apache.org/licenses/LICENSE-2.0) and is covered by [Crown Copyright](https://www.nationalarchives.gov.uk/information-management/re-using-public-sector-information/copyright-and-re-use/crown-copyright/).
|
CyberChef is released under the [Apache 2.0 Licence](https://www.apache.org/licenses/LICENSE-2.0) and is covered by [Crown Copyright](https://www.nationalarchives.gov.uk/information-management/re-using-public-sector-information/uk-government-licensing-framework/crown-copyright/).
|
||||||
|
|
||||||
|
|
||||||
[1]: https://gchq.github.io/CyberChef
|
[1]: https://gchq.github.io/CyberChef
|
||||||
|
@ -11,6 +11,7 @@ module.exports = function(api) {
|
|||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"dynamic-import-node",
|
"dynamic-import-node",
|
||||||
|
"@babel/plugin-syntax-import-assertions",
|
||||||
[
|
[
|
||||||
"babel-plugin-transform-builtin-extend", {
|
"babel-plugin-transform-builtin-extend", {
|
||||||
"globals": ["Error"]
|
"globals": ["Error"]
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"src_folders": ["tests/browser"],
|
"src_folders": ["tests/browser"],
|
||||||
|
"exclude": ["tests/browser/browserUtils.js"],
|
||||||
"output_folder": "tests/browser/output",
|
"output_folder": "tests/browser/output",
|
||||||
|
|
||||||
"test_settings": {
|
"test_settings": {
|
||||||
@ -10,11 +11,12 @@
|
|||||||
"start_process": true,
|
"start_process": true,
|
||||||
"server_path": "./node_modules/.bin/chromedriver",
|
"server_path": "./node_modules/.bin/chromedriver",
|
||||||
"port": 9515,
|
"port": 9515,
|
||||||
"log_path": false
|
"log_path": "tests/browser/output"
|
||||||
},
|
},
|
||||||
"desiredCapabilities": {
|
"desiredCapabilities": {
|
||||||
"browserName": "chrome"
|
"browserName": "chrome"
|
||||||
}
|
},
|
||||||
|
"enable_fail_fast": true
|
||||||
},
|
},
|
||||||
|
|
||||||
"dev": {
|
"dev": {
|
||||||
|
26484
package-lock.json
generated
26484
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
200
package.json
200
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cyberchef",
|
"name": "cyberchef",
|
||||||
"version": "9.28.0",
|
"version": "10.18.8",
|
||||||
"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",
|
||||||
@ -27,154 +27,174 @@
|
|||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/gchq/CyberChef/"
|
"url": "https://github.com/gchq/CyberChef/"
|
||||||
},
|
},
|
||||||
"main": "src/node/cjs.js",
|
"main": "src/node/wrapper.js",
|
||||||
"module": "src/node/index.mjs",
|
"exports": {
|
||||||
|
"import": "./src/node/index.mjs",
|
||||||
|
"require": "./src/node/wrapper.js"
|
||||||
|
},
|
||||||
"bugs": "https://github.com/gchq/CyberChef/issues",
|
"bugs": "https://github.com/gchq/CyberChef/issues",
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"Chrome >= 50",
|
"Chrome >= 50",
|
||||||
"Firefox >= 38",
|
"Firefox >= 38",
|
||||||
"node >= 10"
|
"node >= 16"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.12.16",
|
"@babel/core": "^7.24.7",
|
||||||
"@babel/plugin-transform-runtime": "^7.12.15",
|
"@babel/eslint-parser": "^7.24.7",
|
||||||
"@babel/preset-env": "^7.12.16",
|
"@babel/plugin-syntax-import-assertions": "^7.24.7",
|
||||||
"autoprefixer": "^10.2.4",
|
"@babel/plugin-transform-runtime": "^7.24.7",
|
||||||
"babel-eslint": "^10.1.0",
|
"@babel/preset-env": "^7.24.7",
|
||||||
"babel-loader": "^8.2.2",
|
"@babel/runtime": "^7.24.7",
|
||||||
|
"@codemirror/commands": "^6.6.0",
|
||||||
|
"@codemirror/language": "^6.10.2",
|
||||||
|
"@codemirror/search": "^6.5.6",
|
||||||
|
"@codemirror/state": "^6.4.1",
|
||||||
|
"@codemirror/view": "^6.28.0",
|
||||||
|
"autoprefixer": "^10.4.19",
|
||||||
|
"babel-loader": "^9.1.3",
|
||||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||||
"chromedriver": "^88.0.0",
|
"babel-plugin-transform-builtin-extend": "1.1.2",
|
||||||
"cli-progress": "^3.9.0",
|
"base64-loader": "^1.0.0",
|
||||||
|
"chromedriver": "^125.0.3",
|
||||||
|
"cli-progress": "^3.12.0",
|
||||||
"colors": "^1.4.0",
|
"colors": "^1.4.0",
|
||||||
"copy-webpack-plugin": "^7.0.0",
|
"copy-webpack-plugin": "^12.0.2",
|
||||||
"css-loader": "^5.0.2",
|
"core-js": "^3.37.1",
|
||||||
"eslint": "^7.20.0",
|
"css-loader": "7.1.2",
|
||||||
"exports-loader": "^2.0.0",
|
"eslint": "^9.4.0",
|
||||||
"file-loader": "^6.2.0",
|
"eslint-plugin-jsdoc": "^48.2.9",
|
||||||
"grunt": "^1.3.0",
|
"globals": "^15.4.0",
|
||||||
|
"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.0",
|
"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": "^23.0.0",
|
"grunt-eslint": "^25.0.0",
|
||||||
"grunt-exec": "~3.0.0",
|
"grunt-exec": "~3.0.0",
|
||||||
"grunt-webpack": "^4.0.2",
|
"grunt-webpack": "^6.0.0",
|
||||||
"grunt-zip": "^0.18.2",
|
"grunt-zip": "^1.0.0",
|
||||||
"html-webpack-plugin": "^5.1.0",
|
"html-webpack-plugin": "^5.6.0",
|
||||||
"imports-loader": "^2.0.0",
|
"imports-loader": "^5.0.0",
|
||||||
"mini-css-extract-plugin": "^1.3.7",
|
"mini-css-extract-plugin": "2.9.0",
|
||||||
"nightwatch": "^1.5.1",
|
"modify-source-webpack-plugin": "^4.1.0",
|
||||||
"node-sass": "^5.0.0",
|
"nightwatch": "^3.6.3",
|
||||||
"postcss": "^8.2.6",
|
"postcss": "^8.4.38",
|
||||||
"postcss-css-variables": "^0.17.0",
|
"postcss-css-variables": "^0.19.0",
|
||||||
"postcss-import": "^14.0.0",
|
"postcss-import": "^16.1.0",
|
||||||
"postcss-loader": "^5.0.0",
|
"postcss-loader": "^8.1.1",
|
||||||
"prompt": "^1.1.0",
|
"prompt": "^1.3.0",
|
||||||
"sass-loader": "^11.0.1",
|
"sitemap": "^8.0.0",
|
||||||
"sitemap": "^6.3.6",
|
"terser": "^5.31.1",
|
||||||
"style-loader": "^2.0.0",
|
"webpack": "^5.91.0",
|
||||||
"svg-url-loader": "^7.1.1",
|
"webpack-bundle-analyzer": "^4.10.2",
|
||||||
"url-loader": "^4.1.1",
|
"webpack-dev-server": "5.0.4",
|
||||||
"webpack": "^5.22.0",
|
"webpack-node-externals": "^3.0.0",
|
||||||
"webpack-bundle-analyzer": "^4.4.0",
|
|
||||||
"webpack-dev-server": "^3.11.2",
|
|
||||||
"webpack-node-externals": "^2.5.2",
|
|
||||||
"worker-loader": "^3.0.8"
|
"worker-loader": "^3.0.8"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@astronautlabs/amf": "^0.0.6",
|
||||||
"@babel/polyfill": "^7.12.1",
|
"@babel/polyfill": "^7.12.1",
|
||||||
"@babel/runtime": "^7.12.13",
|
"@blu3r4y/lzma": "^2.3.3",
|
||||||
|
"@wavesenterprise/crypto-gost-js": "^2.1.0-RC1",
|
||||||
|
"@xmldom/xmldom": "^0.8.10",
|
||||||
|
"argon2-browser": "^1.18.0",
|
||||||
"arrive": "^2.4.1",
|
"arrive": "^2.4.1",
|
||||||
"avsc": "^5.5.3",
|
"avsc": "^5.7.7",
|
||||||
"babel-plugin-transform-builtin-extend": "1.1.2",
|
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"bignumber.js": "^9.0.1",
|
"bignumber.js": "^9.1.2",
|
||||||
"blakejs": "^1.1.0",
|
"blakejs": "^1.2.1",
|
||||||
"bootstrap": "4.6.0",
|
"bootstrap": "4.6.2",
|
||||||
"bootstrap-colorpicker": "^3.2.0",
|
"bootstrap-colorpicker": "^3.4.0",
|
||||||
"bootstrap-material-design": "^4.1.3",
|
"bootstrap-material-design": "^4.1.3",
|
||||||
"browserify-zlib": "^0.2.0",
|
"browserify-zlib": "^0.2.0",
|
||||||
"bson": "^4.2.2",
|
"bson": "^4.7.2",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"cbor": "^5.0.1",
|
"cbor": "9.0.2",
|
||||||
"chi-squared": "^1.1.0",
|
"chi-squared": "^1.1.0",
|
||||||
"codepage": "^1.14.0",
|
"codepage": "^1.15.0",
|
||||||
"core-js": "^3.8.3",
|
|
||||||
"crypto-api": "^0.8.5",
|
"crypto-api": "^0.8.5",
|
||||||
"crypto-browserify": "^3.12.0",
|
"crypto-browserify": "^3.12.0",
|
||||||
"crypto-js": "^4.0.0",
|
"crypto-js": "^4.2.0",
|
||||||
"ctph.js": "0.0.5",
|
"ctph.js": "0.0.5",
|
||||||
"d3": "^6.5.0",
|
"d3": "7.9.0",
|
||||||
"d3-hexbin": "^0.2.2",
|
"d3-hexbin": "^0.2.2",
|
||||||
"diff": "^5.0.0",
|
"diff": "^5.2.0",
|
||||||
"es6-promisify": "^6.1.1",
|
"es6-promisify": "^7.0.0",
|
||||||
"escodegen": "^2.0.0",
|
"escodegen": "^2.1.0",
|
||||||
"esm": "^3.2.25",
|
|
||||||
"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": "^10.6.0",
|
"highlight.js": "^11.9.0",
|
||||||
"jimp": "^0.16.1",
|
"ieee754": "^1.2.1",
|
||||||
"jquery": "3.5.1",
|
"jimp": "^0.22.12",
|
||||||
|
"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",
|
||||||
"jsonpath": "^1.1.0",
|
"json5": "^2.2.3",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonpath-plus": "^9.0.0",
|
||||||
"jsqr": "^1.3.1",
|
"jsonwebtoken": "8.5.1",
|
||||||
"jsrsasign": "^10.1.10",
|
"jsqr": "^1.4.0",
|
||||||
|
"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.1.0",
|
"libyara-wasm": "^1.2.1",
|
||||||
"lodash": "^4.17.20",
|
"lodash": "^4.17.21",
|
||||||
"loglevel": "^1.7.1",
|
"loglevel": "^1.9.1",
|
||||||
"loglevel-message-prefix": "^3.0.0",
|
"loglevel-message-prefix": "^3.0.0",
|
||||||
"markdown-it": "^12.0.4",
|
"lz-string": "^1.5.0",
|
||||||
"moment": "^2.29.1",
|
"lz4js": "^0.2.0",
|
||||||
"moment-timezone": "^0.5.33",
|
"markdown-it": "^14.1.0",
|
||||||
|
"moment": "^2.30.1",
|
||||||
|
"moment-timezone": "^0.5.45",
|
||||||
"ngeohash": "^0.6.3",
|
"ngeohash": "^0.6.3",
|
||||||
"node-forge": "^0.10.0",
|
"node-forge": "^1.3.1",
|
||||||
"node-md6": "^0.1.0",
|
"node-md6": "^0.1.0",
|
||||||
"nodom": "^2.4.0",
|
"nodom": "^2.4.0",
|
||||||
"notepack.io": "^2.3.0",
|
"notepack.io": "^3.0.1",
|
||||||
|
"ntlm": "^0.1.3",
|
||||||
"nwmatcher": "^1.4.4",
|
"nwmatcher": "^1.4.4",
|
||||||
"otp": "0.1.3",
|
"otp": "0.1.3",
|
||||||
"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.3.1",
|
||||||
"qr-image": "^3.2.0",
|
"qr-image": "^3.2.0",
|
||||||
|
"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.13.0",
|
"sortablejs": "^1.15.2",
|
||||||
"split.js": "^1.6.2",
|
"split.js": "^1.6.5",
|
||||||
"ssdeep.js": "0.0.2",
|
"ssdeep.js": "0.0.3",
|
||||||
"stream-browserify": "^3.0.0",
|
"stream-browserify": "^3.0.0",
|
||||||
"terser": "^5.6.0",
|
"tesseract.js": "5.1.0",
|
||||||
"tesseract.js": "2.1.1",
|
"ua-parser-js": "^1.0.38",
|
||||||
"ua-parser-js": "^0.7.24",
|
|
||||||
"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.4.0",
|
"xpath": "0.0.34",
|
||||||
"xpath": "0.0.32",
|
"xregexp": "^5.1.1",
|
||||||
"xregexp": "^5.0.1",
|
|
||||||
"zlibjs": "^0.3.1"
|
"zlibjs": "^0.3.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "npx grunt dev",
|
"start": "npx grunt dev",
|
||||||
"build": "npx grunt prod",
|
"build": "npx grunt prod",
|
||||||
"repl": "node src/node/repl.js",
|
"node": "npx grunt node",
|
||||||
"test": "npx grunt configTests && node --experimental-modules --no-warnings --no-deprecation tests/node/index.mjs && node --experimental-modules --no-warnings --no-deprecation tests/operations/index.mjs",
|
"repl": "node --experimental-modules --experimental-json-modules --experimental-specifier-resolution=node --no-experimental-fetch --no-warnings src/node/repl.mjs",
|
||||||
"test-node-consumer": "npx grunt testnodeconsumer",
|
"test": "npx grunt configTests && node --experimental-modules --experimental-json-modules --no-warnings --no-deprecation --openssl-legacy-provider --no-experimental-fetch tests/node/index.mjs && node --experimental-modules --experimental-json-modules --no-warnings --no-deprecation --openssl-legacy-provider --no-experimental-fetch --trace-uncaught tests/operations/index.mjs",
|
||||||
|
"testnodeconsumer": "npx grunt testnodeconsumer",
|
||||||
"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",
|
"postinstall": "npx grunt exec:fixCryptoApiImports && npx grunt exec:fixSnackbarMarkup && npx grunt exec:fixJimpModule",
|
||||||
"newop": "node --experimental-modules src/core/config/scripts/newOperation.mjs",
|
"newop": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newOperation.mjs",
|
||||||
|
"minor": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newMinorVersion.mjs",
|
||||||
"getheapsize": "node -e 'console.log(`node heap limit = ${require(\"v8\").getHeapStatistics().heap_size_limit / (1024 * 1024)} Mb`)'",
|
"getheapsize": "node -e 'console.log(`node heap limit = ${require(\"v8\").getHeapStatistics().heap_size_limit / (1024 * 1024)} Mb`)'",
|
||||||
"setheapsize": "export NODE_OPTIONS=--max_old_space_size=2048"
|
"setheapsize": "export NODE_OPTIONS=--max_old_space_size=2048"
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,8 @@ class Chef {
|
|||||||
*
|
*
|
||||||
* @param {string|ArrayBuffer} input - The input data as a string or ArrayBuffer
|
* @param {string|ArrayBuffer} input - The input data as a string or ArrayBuffer
|
||||||
* @param {Object[]} recipeConfig - The recipe configuration object
|
* @param {Object[]} recipeConfig - The recipe configuration object
|
||||||
* @param {Object} options - The options object storing various user choices
|
* @param {Object} [options={}] - The options object storing various user choices
|
||||||
* @param {boolean} options.attempHighlight - Whether or not to attempt highlighting
|
* @param {string} [options.returnType] - What type to return the result as
|
||||||
*
|
*
|
||||||
* @returns {Object} response
|
* @returns {Object} response
|
||||||
* @returns {string} response.result - The output of the recipe
|
* @returns {string} response.result - The output of the recipe
|
||||||
@ -37,12 +37,11 @@ class Chef {
|
|||||||
* @returns {number} response.duration - The number of ms it took to execute the recipe
|
* @returns {number} response.duration - The number of ms it took to execute the recipe
|
||||||
* @returns {number} response.error - The error object thrown by a failed operation (false if no error)
|
* @returns {number} response.error - The error object thrown by a failed operation (false if no error)
|
||||||
*/
|
*/
|
||||||
async bake(input, recipeConfig, options) {
|
async bake(input, recipeConfig, options={}) {
|
||||||
log.debug("Chef baking");
|
log.debug("Chef baking");
|
||||||
const startTime = Date.now(),
|
const startTime = Date.now(),
|
||||||
recipe = new Recipe(recipeConfig),
|
recipe = new Recipe(recipeConfig),
|
||||||
containsFc = recipe.containsFlowControl(),
|
containsFc = recipe.containsFlowControl();
|
||||||
notUTF8 = options && "treatAsUtf8" in options && !options.treatAsUtf8;
|
|
||||||
let error = false,
|
let error = false,
|
||||||
progress = 0;
|
progress = 0;
|
||||||
|
|
||||||
@ -68,20 +67,13 @@ class Chef {
|
|||||||
// Present the raw result
|
// Present the raw result
|
||||||
await recipe.present(this.dish);
|
await recipe.present(this.dish);
|
||||||
|
|
||||||
// Depending on the size of the output, we may send it back as a string or an ArrayBuffer.
|
|
||||||
// This can prevent unnecessary casting as an ArrayBuffer can be easily downloaded as a file.
|
|
||||||
// The threshold is specified in KiB.
|
|
||||||
const threshold = (options.ioDisplayThreshold || 1024) * 1024;
|
|
||||||
const returnType =
|
const returnType =
|
||||||
this.dish.type === Dish.HTML ?
|
this.dish.type === Dish.HTML ? Dish.HTML :
|
||||||
Dish.HTML :
|
options?.returnType ? options.returnType : Dish.ARRAY_BUFFER;
|
||||||
this.dish.size > threshold ?
|
|
||||||
Dish.ARRAY_BUFFER :
|
|
||||||
Dish.STRING;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dish: rawDish,
|
dish: rawDish,
|
||||||
result: await this.dish.get(returnType, notUTF8),
|
result: await this.dish.get(returnType),
|
||||||
type: Dish.enumLookup(this.dish.type),
|
type: Dish.enumLookup(this.dish.type),
|
||||||
progress: progress,
|
progress: progress,
|
||||||
duration: Date.now() - startTime,
|
duration: Date.now() - startTime,
|
||||||
|
@ -7,18 +7,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Chef from "./Chef.mjs";
|
import Chef from "./Chef.mjs";
|
||||||
import OperationConfig from "./config/OperationConfig.json";
|
import OperationConfig from "./config/OperationConfig.json" assert {type: "json"};
|
||||||
import OpModules from "./config/modules/OpModules.mjs";
|
import OpModules from "./config/modules/OpModules.mjs";
|
||||||
|
|
||||||
// Add ">" to the start of all log messages in the Chef Worker
|
|
||||||
import loglevelMessagePrefix from "loglevel-message-prefix";
|
import loglevelMessagePrefix from "loglevel-message-prefix";
|
||||||
|
|
||||||
loglevelMessagePrefix(log, {
|
|
||||||
prefixes: [],
|
|
||||||
staticPrefixes: [">"],
|
|
||||||
prefixFormat: "%p"
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// Set up Chef instance
|
// Set up Chef instance
|
||||||
self.chef = new Chef();
|
self.chef = new Chef();
|
||||||
@ -56,7 +48,7 @@ self.postMessage({
|
|||||||
self.addEventListener("message", function(e) {
|
self.addEventListener("message", function(e) {
|
||||||
// Handle message
|
// Handle message
|
||||||
const r = e.data;
|
const r = e.data;
|
||||||
log.debug("ChefWorker receiving command '" + r.action + "'");
|
log.debug(`Receiving command '${r.action}'`);
|
||||||
|
|
||||||
switch (r.action) {
|
switch (r.action) {
|
||||||
case "bake":
|
case "bake":
|
||||||
@ -86,6 +78,12 @@ self.addEventListener("message", function(e) {
|
|||||||
case "setLogLevel":
|
case "setLogLevel":
|
||||||
log.setLevel(r.data, false);
|
log.setLevel(r.data, false);
|
||||||
break;
|
break;
|
||||||
|
case "setLogPrefix":
|
||||||
|
loglevelMessagePrefix(log, {
|
||||||
|
prefixes: [],
|
||||||
|
staticPrefixes: [r.data]
|
||||||
|
});
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -101,14 +99,17 @@ async function bake(data) {
|
|||||||
// Ensure the relevant modules are loaded
|
// Ensure the relevant modules are loaded
|
||||||
self.loadRequiredModules(data.recipeConfig);
|
self.loadRequiredModules(data.recipeConfig);
|
||||||
try {
|
try {
|
||||||
self.inputNum = (data.inputNum !== undefined) ? data.inputNum : -1;
|
self.inputNum = data.inputNum === undefined ? -1 : data.inputNum;
|
||||||
const response = await self.chef.bake(
|
const response = await self.chef.bake(
|
||||||
data.input, // The user's input
|
data.input, // The user's input
|
||||||
data.recipeConfig, // The configuration of the recipe
|
data.recipeConfig, // The configuration of the recipe
|
||||||
data.options // Options set by the user
|
data.options // Options set by the user
|
||||||
);
|
);
|
||||||
|
|
||||||
const transferable = (data.input instanceof ArrayBuffer) ? [data.input] : undefined;
|
const transferable = (response.dish.value instanceof ArrayBuffer) ?
|
||||||
|
[response.dish.value] :
|
||||||
|
undefined;
|
||||||
|
|
||||||
self.postMessage({
|
self.postMessage({
|
||||||
action: "bakeComplete",
|
action: "bakeComplete",
|
||||||
data: Object.assign(response, {
|
data: Object.assign(response, {
|
||||||
@ -186,7 +187,7 @@ async function getDishTitle(data) {
|
|||||||
*
|
*
|
||||||
* @param {Object[]} recipeConfig
|
* @param {Object[]} recipeConfig
|
||||||
* @param {string} direction
|
* @param {string} direction
|
||||||
* @param {Object} pos - The position object for the highlight.
|
* @param {Object[]} pos - The position object for the highlight.
|
||||||
* @param {number} pos.start - The start offset.
|
* @param {number} pos.start - The start offset.
|
||||||
* @param {number} pos.end - The end offset.
|
* @param {number} pos.end - The end offset.
|
||||||
*/
|
*/
|
||||||
|
@ -128,10 +128,9 @@ class Dish {
|
|||||||
* If running in a browser, get is asynchronous.
|
* If running in a browser, get is asynchronous.
|
||||||
*
|
*
|
||||||
* @param {number} type - The data type of value, see Dish enums.
|
* @param {number} type - The data type of value, see Dish enums.
|
||||||
* @param {boolean} [notUTF8=false] - Do not treat strings as UTF8.
|
|
||||||
* @returns {* | Promise} - (Browser) A promise | (Node) value of dish in given type
|
* @returns {* | Promise} - (Browser) A promise | (Node) value of dish in given type
|
||||||
*/
|
*/
|
||||||
get(type, notUTF8=false) {
|
get(type) {
|
||||||
if (typeof type === "string") {
|
if (typeof type === "string") {
|
||||||
type = Dish.typeEnum(type);
|
type = Dish.typeEnum(type);
|
||||||
}
|
}
|
||||||
@ -140,13 +139,13 @@ class Dish {
|
|||||||
|
|
||||||
// Node environment => _translate is sync
|
// Node environment => _translate is sync
|
||||||
if (isNodeEnvironment()) {
|
if (isNodeEnvironment()) {
|
||||||
this._translate(type, notUTF8);
|
this._translate(type);
|
||||||
return this.value;
|
return this.value;
|
||||||
|
|
||||||
// Browser environment => _translate is async
|
// Browser environment => _translate is async
|
||||||
} else {
|
} else {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this._translate(type, notUTF8)
|
this._translate(type)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
resolve(this.value);
|
resolve(this.value);
|
||||||
})
|
})
|
||||||
@ -190,12 +189,11 @@ class Dish {
|
|||||||
* @Node
|
* @Node
|
||||||
*
|
*
|
||||||
* @param {number} type - The data type of value, see Dish enums.
|
* @param {number} type - The data type of value, see Dish enums.
|
||||||
* @param {boolean} [notUTF8=false] - Do not treat strings as UTF8.
|
|
||||||
* @returns {Dish | Promise} - (Browser) A promise | (Node) value of dish in given type
|
* @returns {Dish | Promise} - (Browser) A promise | (Node) value of dish in given type
|
||||||
*/
|
*/
|
||||||
presentAs(type, notUTF8=false) {
|
presentAs(type) {
|
||||||
const clone = this.clone();
|
const clone = this.clone();
|
||||||
return clone.get(type, notUTF8);
|
return clone.get(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -414,17 +412,16 @@ class Dish {
|
|||||||
* If running in the browser, _translate is asynchronous.
|
* If running in the browser, _translate is asynchronous.
|
||||||
*
|
*
|
||||||
* @param {number} toType - The data type of value, see Dish enums.
|
* @param {number} toType - The data type of value, see Dish enums.
|
||||||
* @param {boolean} [notUTF8=false] - Do not treat strings as UTF8.
|
|
||||||
* @returns {Promise || undefined}
|
* @returns {Promise || undefined}
|
||||||
*/
|
*/
|
||||||
_translate(toType, notUTF8=false) {
|
_translate(toType) {
|
||||||
log.debug(`Translating Dish from ${Dish.enumLookup(this.type)} to ${Dish.enumLookup(toType)}`);
|
log.debug(`Translating Dish from ${Dish.enumLookup(this.type)} to ${Dish.enumLookup(toType)}`);
|
||||||
|
|
||||||
// Node environment => translate is sync
|
// Node environment => translate is sync
|
||||||
if (isNodeEnvironment()) {
|
if (isNodeEnvironment()) {
|
||||||
this._toArrayBuffer();
|
this._toArrayBuffer();
|
||||||
this.type = Dish.ARRAY_BUFFER;
|
this.type = Dish.ARRAY_BUFFER;
|
||||||
this._fromArrayBuffer(toType, notUTF8);
|
this._fromArrayBuffer(toType);
|
||||||
|
|
||||||
// Browser environment => translate is async
|
// Browser environment => translate is async
|
||||||
} else {
|
} else {
|
||||||
@ -486,18 +483,17 @@ class Dish {
|
|||||||
* Convert this.value to the given type from ArrayBuffer
|
* Convert this.value to the given type from ArrayBuffer
|
||||||
*
|
*
|
||||||
* @param {number} toType - the Dish enum to convert to
|
* @param {number} toType - the Dish enum to convert to
|
||||||
* @param {boolean} [notUTF8=false] - Do not treat strings as UTF8.
|
|
||||||
*/
|
*/
|
||||||
_fromArrayBuffer(toType, notUTF8) {
|
_fromArrayBuffer(toType) {
|
||||||
|
|
||||||
// Using 'bind' here to allow this.value to be mutated within translation functions
|
// Using 'bind' here to allow this.value to be mutated within translation functions
|
||||||
const toTypeFunctions = {
|
const toTypeFunctions = {
|
||||||
[Dish.STRING]: () => DishString.fromArrayBuffer.bind(this)(notUTF8),
|
[Dish.STRING]: () => DishString.fromArrayBuffer.bind(this)(),
|
||||||
[Dish.NUMBER]: () => DishNumber.fromArrayBuffer.bind(this)(notUTF8),
|
[Dish.NUMBER]: () => DishNumber.fromArrayBuffer.bind(this)(),
|
||||||
[Dish.HTML]: () => DishHTML.fromArrayBuffer.bind(this)(notUTF8),
|
[Dish.HTML]: () => DishHTML.fromArrayBuffer.bind(this)(),
|
||||||
[Dish.ARRAY_BUFFER]: () => {},
|
[Dish.ARRAY_BUFFER]: () => {},
|
||||||
[Dish.BIG_NUMBER]: () => DishBigNumber.fromArrayBuffer.bind(this)(notUTF8),
|
[Dish.BIG_NUMBER]: () => DishBigNumber.fromArrayBuffer.bind(this)(),
|
||||||
[Dish.JSON]: () => DishJSON.fromArrayBuffer.bind(this)(notUTF8),
|
[Dish.JSON]: () => DishJSON.fromArrayBuffer.bind(this)(),
|
||||||
[Dish.FILE]: () => DishFile.fromArrayBuffer.bind(this)(),
|
[Dish.FILE]: () => DishFile.fromArrayBuffer.bind(this)(),
|
||||||
[Dish.LIST_FILE]: () => DishListFile.fromArrayBuffer.bind(this)(),
|
[Dish.LIST_FILE]: () => DishListFile.fromArrayBuffer.bind(this)(),
|
||||||
[Dish.BYTE_ARRAY]: () => DishByteArray.fromArrayBuffer.bind(this)(),
|
[Dish.BYTE_ARRAY]: () => DishByteArray.fromArrayBuffer.bind(this)(),
|
||||||
|
@ -27,6 +27,7 @@ class Ingredient {
|
|||||||
this.toggleValues = [];
|
this.toggleValues = [];
|
||||||
this.target = null;
|
this.target = null;
|
||||||
this.defaultIndex = 0;
|
this.defaultIndex = 0;
|
||||||
|
this.maxLength = null;
|
||||||
this.min = null;
|
this.min = null;
|
||||||
this.max = null;
|
this.max = null;
|
||||||
this.step = 1;
|
this.step = 1;
|
||||||
@ -53,6 +54,7 @@ class Ingredient {
|
|||||||
this.toggleValues = ingredientConfig.toggleValues;
|
this.toggleValues = ingredientConfig.toggleValues;
|
||||||
this.target = typeof ingredientConfig.target !== "undefined" ? ingredientConfig.target : null;
|
this.target = typeof ingredientConfig.target !== "undefined" ? ingredientConfig.target : null;
|
||||||
this.defaultIndex = typeof ingredientConfig.defaultIndex !== "undefined" ? ingredientConfig.defaultIndex : 0;
|
this.defaultIndex = typeof ingredientConfig.defaultIndex !== "undefined" ? ingredientConfig.defaultIndex : 0;
|
||||||
|
this.maxLength = ingredientConfig.maxLength || null;
|
||||||
this.min = ingredientConfig.min;
|
this.min = ingredientConfig.min;
|
||||||
this.max = ingredientConfig.max;
|
this.max = ingredientConfig.max;
|
||||||
this.step = ingredientConfig.step;
|
this.step = ingredientConfig.step;
|
||||||
|
@ -184,6 +184,7 @@ class Operation {
|
|||||||
if (ing.disabled) conf.disabled = ing.disabled;
|
if (ing.disabled) conf.disabled = ing.disabled;
|
||||||
if (ing.target) conf.target = ing.target;
|
if (ing.target) conf.target = ing.target;
|
||||||
if (ing.defaultIndex) conf.defaultIndex = ing.defaultIndex;
|
if (ing.defaultIndex) conf.defaultIndex = ing.defaultIndex;
|
||||||
|
if (ing.maxLength) conf.maxLength = ing.maxLength;
|
||||||
if (typeof ing.min === "number") conf.min = ing.min;
|
if (typeof ing.min === "number") conf.min = ing.min;
|
||||||
if (typeof ing.max === "number") conf.max = ing.max;
|
if (typeof ing.max === "number") conf.max = ing.max;
|
||||||
if (ing.step) conf.step = ing.step;
|
if (ing.step) conf.step = ing.step;
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
* @license Apache-2.0
|
* @license Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import OperationConfig from "./config/OperationConfig.json";
|
import OperationConfig from "./config/OperationConfig.json" assert {type: "json"};
|
||||||
import OperationError from "./errors/OperationError.mjs";
|
import OperationError from "./errors/OperationError.mjs";
|
||||||
import Operation from "./Operation.mjs";
|
import Operation from "./Operation.mjs";
|
||||||
import DishError from "./errors/DishError.mjs";
|
import DishError from "./errors/DishError.mjs";
|
||||||
@ -230,14 +230,12 @@ class Recipe {
|
|||||||
this.lastRunOp = op;
|
this.lastRunOp = op;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Return expected errors as output
|
// Return expected errors as output
|
||||||
if (err instanceof OperationError ||
|
if (err instanceof OperationError || err?.type === "OperationError") {
|
||||||
(err.type && err.type === "OperationError")) {
|
|
||||||
// Cannot rely on `err instanceof OperationError` here as extending
|
// Cannot rely on `err instanceof OperationError` here as extending
|
||||||
// native types is not fully supported yet.
|
// native types is not fully supported yet.
|
||||||
dish.set(err.message, "string");
|
dish.set(err.message, "string");
|
||||||
return i;
|
return i;
|
||||||
} else if (err instanceof DishError ||
|
} else if (err instanceof DishError || err?.type === "DishError") {
|
||||||
(err.type && err.type === "DishError")) {
|
|
||||||
dish.set(err.message, "string");
|
dish.set(err.message, "string");
|
||||||
return i;
|
return i;
|
||||||
} else {
|
} else {
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
* @license Apache-2.0
|
* @license Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// loglevel import required for Node API
|
||||||
|
import log from "loglevel";
|
||||||
import utf8 from "utf8";
|
import utf8 from "utf8";
|
||||||
import {fromBase64, toBase64} from "./lib/Base64.mjs";
|
import {fromBase64, toBase64} from "./lib/Base64.mjs";
|
||||||
import {fromHex} from "./lib/Hex.mjs";
|
import {fromHex} from "./lib/Hex.mjs";
|
||||||
@ -174,17 +176,13 @@ class Utils {
|
|||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
static printable(str, preserveWs=false, onlyAscii=false) {
|
static printable(str, preserveWs=false, onlyAscii=false) {
|
||||||
if (isWebEnvironment() && window.app && !window.app.options.treatAsUtf8) {
|
|
||||||
str = Utils.byteArrayToChars(Utils.strToByteArray(str));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onlyAscii) {
|
if (onlyAscii) {
|
||||||
return str.replace(/[^\x20-\x7f]/g, ".");
|
return str.replace(/[^\x20-\x7f]/g, ".");
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-misleading-character-class
|
// eslint-disable-next-line no-misleading-character-class
|
||||||
const re = /[\0-\x08\x0B-\x0C\x0E-\x1F\x7F-\x9F\xAD\u0378\u0379\u037F-\u0383\u038B\u038D\u03A2\u0528-\u0530\u0557\u0558\u0560\u0588\u058B-\u058E\u0590\u05C8-\u05CF\u05EB-\u05EF\u05F5-\u0605\u061C\u061D\u06DD\u070E\u070F\u074B\u074C\u07B2-\u07BF\u07FB-\u07FF\u082E\u082F\u083F\u085C\u085D\u085F-\u089F\u08A1\u08AD-\u08E3\u08FF\u0978\u0980\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FC-\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B55\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0C00\u0C04\u0C0D\u0C11\u0C29\u0C34\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5A-\u0C5F\u0C64\u0C65\u0C70-\u0C77\u0C80\u0C81\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0D01\u0D04\u0D0D\u0D11\u0D3B\u0D3C\u0D45\u0D49\u0D4F-\u0D56\u0D58-\u0D5F\u0D64\u0D65\u0D76-\u0D78\u0D80\u0D81\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E86\u0E89\u0E8B\u0E8C\u0E8E-\u0E93\u0E98\u0EA0\u0EA4\u0EA6\u0EA8\u0EA9\u0EAC\u0EBA\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F5-\u13FF\u169D-\u169F\u16F1-\u16FF\u170D\u1715-\u171F\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u180F\u181A-\u181F\u1878-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191D-\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE-\u1AFF\u1B4C-\u1B4F\u1B7D-\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C80-\u1CBF\u1CC8-\u1CCF\u1CF7-\u1CFF\u1DE7-\u1DFB\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u200B-\u200F\u202A-\u202E\u2060-\u206F\u2072\u2073\u208F\u209D-\u209F\u20BB-\u20CF\u20F1-\u20FF\u218A-\u218F\u23F4-\u23FF\u2427-\u243F\u244B-\u245F\u2700\u2B4D-\u2B4F\u2B5A-\u2BFF\u2C2F\u2C5F\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E3C-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u312E-\u3130\u318F\u31BB-\u31BF\u31E4-\u31EF\u321F\u32FF\u4DB6-\u4DBF\u9FCD-\u9FFF\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA698-\uA69E\uA6F8-\uA6FF\uA78F\uA794-\uA79F\uA7AB-\uA7F7\uA82C-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C5-\uA8CD\uA8DA-\uA8DF\uA8FC-\uA8FF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9E0-\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAA7C-\uAA7F\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F-\uABBF\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uD7FF\uE000-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC2-\uFBD2\uFD40-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFE\uFDFF\uFE1A-\uFE1F\uFE27-\uFE2F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD-\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFFB\uFFFE\uFFFF]/g;
|
const re = /[\0-\x08\x0B-\x0C\x0E-\x1F\x7F-\x9F\xAD\u0378\u0379\u037F-\u0383\u038B\u038D\u03A2\u0528-\u0530\u0557\u0558\u0560\u0588\u058B-\u058E\u0590\u05C8-\u05CF\u05EB-\u05EF\u05F5-\u0605\u061C\u061D\u06DD\u070E\u070F\u074B\u074C\u07B2-\u07BF\u07FB-\u07FF\u082E\u082F\u083F\u085C\u085D\u085F-\u089F\u08A1\u08AD-\u08E3\u08FF\u0978\u0980\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FC-\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B55\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0C00\u0C04\u0C0D\u0C11\u0C29\u0C34\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5A-\u0C5F\u0C64\u0C65\u0C70-\u0C77\u0C80\u0C81\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0D01\u0D04\u0D0D\u0D11\u0D3B\u0D3C\u0D45\u0D49\u0D4F-\u0D56\u0D58-\u0D5F\u0D64\u0D65\u0D76-\u0D78\u0D80\u0D81\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E86\u0E89\u0E8B\u0E8C\u0E8E-\u0E93\u0E98\u0EA0\u0EA4\u0EA6\u0EA8\u0EA9\u0EAC\u0EBA\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F5-\u13FF\u169D-\u169F\u16F1-\u16FF\u170D\u1715-\u171F\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u180F\u181A-\u181F\u1878-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191D-\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE-\u1AFF\u1B4C-\u1B4F\u1B7D-\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C80-\u1CBF\u1CC8-\u1CCF\u1CF7-\u1CFF\u1DE7-\u1DFB\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u200B-\u200F\u202A-\u202E\u2060-\u206F\u2072\u2073\u208F\u209D-\u209F\u20BB-\u20CF\u20F1-\u20FF\u218A-\u218F\u23F4-\u23FF\u2427-\u243F\u244B-\u245F\u2700\u2B4D-\u2B4F\u2B5A-\u2BFF\u2C2F\u2C5F\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E3C-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u312E-\u3130\u318F\u31BB-\u31BF\u31E4-\u31EF\u321F\u32FF\u4DB6-\u4DBF\u9FCD-\u9FFF\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA698-\uA69E\uA6F8-\uA6FF\uA78F\uA794-\uA79F\uA7AB-\uA7F7\uA82C-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C5-\uA8CD\uA8DA-\uA8DF\uA8FC-\uA8FF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9E0-\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAA7C-\uAA7F\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F-\uABBF\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uD7FF\uE000-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC2-\uFBD2\uFD40-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFE\uFDFF\uFE1A-\uFE1F\uFE27-\uFE2F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD-\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFFB\uFFFE\uFFFF]/g;
|
||||||
const wsRe = /[\x09-\x10\x0D\u2028\u2029]/g;
|
const wsRe = /[\x09-\x10\u2028\u2029]/g;
|
||||||
|
|
||||||
str = str.replace(re, ".");
|
str = str.replace(re, ".");
|
||||||
if (!preserveWs) str = str.replace(wsRe, ".");
|
if (!preserveWs) str = str.replace(wsRe, ".");
|
||||||
@ -192,6 +190,21 @@ class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string with whitespace represented as special characters from the
|
||||||
|
* Unicode Private Use Area, which CyberChef will display as control characters.
|
||||||
|
* Private Use Area characters are in the range U+E000..U+F8FF.
|
||||||
|
* https://en.wikipedia.org/wiki/Private_Use_Areas
|
||||||
|
* @param {string} str
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
static escapeWhitespace(str) {
|
||||||
|
return str.replace(/[\x09-\x10]/g, function(c) {
|
||||||
|
return String.fromCharCode(0xe000 + c.charCodeAt(0));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a string entered by a user and replace escaped chars with the bytes they represent.
|
* Parse a string entered by a user and replace escaped chars with the bytes they represent.
|
||||||
*
|
*
|
||||||
@ -206,7 +219,7 @@ class Utils {
|
|||||||
* Utils.parseEscapedChars("\\n");
|
* Utils.parseEscapedChars("\\n");
|
||||||
*/
|
*/
|
||||||
static parseEscapedChars(str) {
|
static parseEscapedChars(str) {
|
||||||
return str.replace(/\\([bfnrtv'"]|[0-3][0-7]{2}|[0-7]{1,2}|x[\da-fA-F]{2}|u[\da-fA-F]{4}|u\{[\da-fA-F]{1,6}\}|\\)/g, function(m, a) {
|
return str.replace(/\\([abfnrtv'"]|[0-3][0-7]{2}|[0-7]{1,2}|x[\da-fA-F]{2}|u[\da-fA-F]{4}|u\{[\da-fA-F]{1,6}\}|\\)/g, function(m, a) {
|
||||||
switch (a[0]) {
|
switch (a[0]) {
|
||||||
case "\\":
|
case "\\":
|
||||||
return "\\";
|
return "\\";
|
||||||
@ -219,6 +232,8 @@ class Utils {
|
|||||||
case "6":
|
case "6":
|
||||||
case "7":
|
case "7":
|
||||||
return String.fromCharCode(parseInt(a, 8));
|
return String.fromCharCode(parseInt(a, 8));
|
||||||
|
case "a":
|
||||||
|
return String.fromCharCode(7);
|
||||||
case "b":
|
case "b":
|
||||||
return "\b";
|
return "\b";
|
||||||
case "t":
|
case "t":
|
||||||
@ -380,6 +395,70 @@ class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a byte array to an integer.
|
||||||
|
*
|
||||||
|
* @param {byteArray} byteArray
|
||||||
|
* @param {string} byteorder - "little" or "big"
|
||||||
|
* @returns {integer}
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // returns 67305985
|
||||||
|
* Utils.byteArrayToInt([1, 2, 3, 4], "little");
|
||||||
|
*
|
||||||
|
* // returns 16909060
|
||||||
|
* Utils.byteArrayToInt([1, 2, 3, 4], "big");
|
||||||
|
*/
|
||||||
|
static byteArrayToInt(byteArray, byteorder) {
|
||||||
|
let value = 0;
|
||||||
|
if (byteorder === "big") {
|
||||||
|
for (let i = 0; i < byteArray.length; i++) {
|
||||||
|
value = (value * 256) + byteArray[i];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = byteArray.length - 1; i >= 0; i--) {
|
||||||
|
value = (value * 256) + byteArray[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an integer to a byte array of {length} bytes.
|
||||||
|
*
|
||||||
|
* @param {integer} value
|
||||||
|
* @param {integer} length
|
||||||
|
* @param {string} byteorder - "little" or "big"
|
||||||
|
* @returns {byteArray}
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // returns [5, 255, 109, 1]
|
||||||
|
* Utils.intToByteArray(23985925, 4, "little");
|
||||||
|
*
|
||||||
|
* // returns [1, 109, 255, 5]
|
||||||
|
* Utils.intToByteArray(23985925, 4, "big");
|
||||||
|
*
|
||||||
|
* // returns [0, 0, 0, 0, 1, 109, 255, 5]
|
||||||
|
* Utils.intToByteArray(23985925, 8, "big");
|
||||||
|
*/
|
||||||
|
static intToByteArray(value, length, byteorder) {
|
||||||
|
const arr = new Array(length);
|
||||||
|
if (byteorder === "little") {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
arr[i] = value & 0xFF;
|
||||||
|
value = value >>> 8;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = length - 1; i >= 0; i--) {
|
||||||
|
arr[i] = value & 0xFF;
|
||||||
|
value = value >>> 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a string to an ArrayBuffer.
|
* Converts a string to an ArrayBuffer.
|
||||||
* Treats the string as UTF-8 if any values are over 255.
|
* Treats the string as UTF-8 if any values are over 255.
|
||||||
@ -395,6 +474,9 @@ class Utils {
|
|||||||
* Utils.strToArrayBuffer("你好");
|
* Utils.strToArrayBuffer("你好");
|
||||||
*/
|
*/
|
||||||
static strToArrayBuffer(str) {
|
static strToArrayBuffer(str) {
|
||||||
|
log.debug(`Converting string[${str?.length}] to array buffer`);
|
||||||
|
if (!str) return new ArrayBuffer;
|
||||||
|
|
||||||
const arr = new Uint8Array(str.length);
|
const arr = new Uint8Array(str.length);
|
||||||
let i = str.length, b;
|
let i = str.length, b;
|
||||||
while (i--) {
|
while (i--) {
|
||||||
@ -421,17 +503,20 @@ class Utils {
|
|||||||
* Utils.strToUtf8ArrayBuffer("你好");
|
* Utils.strToUtf8ArrayBuffer("你好");
|
||||||
*/
|
*/
|
||||||
static strToUtf8ArrayBuffer(str) {
|
static strToUtf8ArrayBuffer(str) {
|
||||||
const utf8Str = utf8.encode(str);
|
log.debug(`Converting string[${str?.length}] to UTF8 array buffer`);
|
||||||
|
if (!str) return new ArrayBuffer;
|
||||||
|
|
||||||
if (str.length !== utf8Str.length) {
|
const buffer = new TextEncoder("utf-8").encode(str);
|
||||||
if (isWorkerEnvironment()) {
|
|
||||||
|
if (str.length !== buffer.length) {
|
||||||
|
if (isWorkerEnvironment() && self && typeof self.setOption === "function") {
|
||||||
self.setOption("attemptHighlight", false);
|
self.setOption("attemptHighlight", false);
|
||||||
} else if (isWebEnvironment()) {
|
} else if (isWebEnvironment()) {
|
||||||
window.app.options.attemptHighlight = false;
|
window.app.options.attemptHighlight = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Utils.strToArrayBuffer(utf8Str);
|
return buffer.buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -450,6 +535,8 @@ class Utils {
|
|||||||
* Utils.strToByteArray("你好");
|
* Utils.strToByteArray("你好");
|
||||||
*/
|
*/
|
||||||
static strToByteArray(str) {
|
static strToByteArray(str) {
|
||||||
|
log.debug(`Converting string[${str?.length}] to byte array`);
|
||||||
|
if (!str) return [];
|
||||||
const byteArray = new Array(str.length);
|
const byteArray = new Array(str.length);
|
||||||
let i = str.length, b;
|
let i = str.length, b;
|
||||||
while (i--) {
|
while (i--) {
|
||||||
@ -476,6 +563,8 @@ class Utils {
|
|||||||
* Utils.strToUtf8ByteArray("你好");
|
* Utils.strToUtf8ByteArray("你好");
|
||||||
*/
|
*/
|
||||||
static strToUtf8ByteArray(str) {
|
static strToUtf8ByteArray(str) {
|
||||||
|
log.debug(`Converting string[${str?.length}] to UTF8 byte array`);
|
||||||
|
if (!str) return [];
|
||||||
const utf8Str = utf8.encode(str);
|
const utf8Str = utf8.encode(str);
|
||||||
|
|
||||||
if (str.length !== utf8Str.length) {
|
if (str.length !== utf8Str.length) {
|
||||||
@ -504,6 +593,8 @@ class Utils {
|
|||||||
* Utils.strToCharcode("你好");
|
* Utils.strToCharcode("你好");
|
||||||
*/
|
*/
|
||||||
static strToCharcode(str) {
|
static strToCharcode(str) {
|
||||||
|
log.debug(`Converting string[${str?.length}] to charcode`);
|
||||||
|
if (!str) return [];
|
||||||
const charcode = [];
|
const charcode = [];
|
||||||
|
|
||||||
for (let i = 0; i < str.length; i++) {
|
for (let i = 0; i < str.length; i++) {
|
||||||
@ -538,20 +629,26 @@ class Utils {
|
|||||||
* Utils.byteArrayToUtf8([228,189,160,229,165,189]);
|
* Utils.byteArrayToUtf8([228,189,160,229,165,189]);
|
||||||
*/
|
*/
|
||||||
static byteArrayToUtf8(byteArray) {
|
static byteArrayToUtf8(byteArray) {
|
||||||
const str = Utils.byteArrayToChars(byteArray);
|
log.debug(`Converting byte array[${byteArray?.length}] to UTF8`);
|
||||||
|
if (!byteArray || !byteArray.length) return "";
|
||||||
|
if (!(byteArray instanceof Uint8Array))
|
||||||
|
byteArray = new Uint8Array(byteArray);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const utf8Str = utf8.decode(str);
|
const str = new TextDecoder("utf-8", {fatal: true}).decode(byteArray);
|
||||||
if (str.length !== utf8Str.length) {
|
|
||||||
|
if (str.length !== byteArray.length) {
|
||||||
if (isWorkerEnvironment()) {
|
if (isWorkerEnvironment()) {
|
||||||
self.setOption("attemptHighlight", false);
|
self.setOption("attemptHighlight", false);
|
||||||
} else if (isWebEnvironment()) {
|
} else if (isWebEnvironment()) {
|
||||||
window.app.options.attemptHighlight = false;
|
window.app.options.attemptHighlight = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return utf8Str;
|
|
||||||
|
return str;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// If it fails, treat it as ANSI
|
// If it fails, treat it as ANSI
|
||||||
return str;
|
return Utils.byteArrayToChars(byteArray);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -570,11 +667,13 @@ class Utils {
|
|||||||
* Utils.byteArrayToChars([20320,22909]);
|
* Utils.byteArrayToChars([20320,22909]);
|
||||||
*/
|
*/
|
||||||
static byteArrayToChars(byteArray) {
|
static byteArrayToChars(byteArray) {
|
||||||
if (!byteArray) return "";
|
log.debug(`Converting byte array[${byteArray?.length}] to chars`);
|
||||||
|
if (!byteArray || !byteArray.length) return "";
|
||||||
let str = "";
|
let str = "";
|
||||||
// String concatenation appears to be faster than an array join
|
// Maxiumum arg length for fromCharCode is 65535, but the stack may already be fairly deep,
|
||||||
for (let i = 0; i < byteArray.length;) {
|
// so don't get too near it.
|
||||||
str += String.fromCharCode(byteArray[i++]);
|
for (let i = 0; i < byteArray.length; i += 20000) {
|
||||||
|
str += String.fromCharCode(...(byteArray.slice(i, i+20000)));
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
@ -592,6 +691,8 @@ class Utils {
|
|||||||
* Utils.arrayBufferToStr(Uint8Array.from([104,101,108,108,111]).buffer);
|
* Utils.arrayBufferToStr(Uint8Array.from([104,101,108,108,111]).buffer);
|
||||||
*/
|
*/
|
||||||
static arrayBufferToStr(arrayBuffer, utf8=true) {
|
static arrayBufferToStr(arrayBuffer, utf8=true) {
|
||||||
|
log.debug(`Converting array buffer[${arrayBuffer?.byteLength}] to str`);
|
||||||
|
if (!arrayBuffer || !arrayBuffer.byteLength) return "";
|
||||||
const arr = new Uint8Array(arrayBuffer);
|
const arr = new Uint8Array(arrayBuffer);
|
||||||
return utf8 ? Utils.byteArrayToUtf8(arr) : Utils.byteArrayToChars(arr);
|
return utf8 ? Utils.byteArrayToUtf8(arr) : Utils.byteArrayToChars(arr);
|
||||||
}
|
}
|
||||||
@ -723,9 +824,10 @@ class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (removeScriptAndStyle) {
|
if (removeScriptAndStyle) {
|
||||||
htmlStr = recursiveRemove(/<(script|style)[^>]*>.*?<\/(script|style)>/gi, htmlStr);
|
htmlStr = recursiveRemove(/<script[^>]*>(\s|\S)*?<\/script[^>]*>/gi, htmlStr);
|
||||||
|
htmlStr = recursiveRemove(/<style[^>]*>(\s|\S)*?<\/style[^>]*>/gi, htmlStr);
|
||||||
}
|
}
|
||||||
return htmlStr.replace(/<[^>]+>/g, "");
|
return recursiveRemove(/<[^>]+>/g, htmlStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -733,6 +835,11 @@ class Utils {
|
|||||||
* Escapes HTML tags in a string to stop them being rendered.
|
* Escapes HTML tags in a string to stop them being rendered.
|
||||||
* https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet
|
* https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet
|
||||||
*
|
*
|
||||||
|
* Null bytes are a special case and are converted to a character from the Unicode
|
||||||
|
* Private Use Area, which CyberChef will display as a control character picture.
|
||||||
|
* This is done due to null bytes not being rendered or stored correctly in HTML
|
||||||
|
* DOM building.
|
||||||
|
*
|
||||||
* @param {string} str
|
* @param {string} str
|
||||||
* @returns string
|
* @returns string
|
||||||
*
|
*
|
||||||
@ -747,12 +854,13 @@ class Utils {
|
|||||||
">": ">",
|
">": ">",
|
||||||
'"': """,
|
'"': """,
|
||||||
"'": "'", // ' not recommended because it's not in the HTML spec
|
"'": "'", // ' not recommended because it's not in the HTML spec
|
||||||
"`": "`"
|
"`": "`",
|
||||||
|
"\u0000": "\ue000"
|
||||||
};
|
};
|
||||||
|
|
||||||
return str.replace(/[&<>"'`]/g, function (match) {
|
return str ? str.replace(/[&<>"'`\u0000]/g, function (match) {
|
||||||
return HTML_CHARS[match];
|
return HTML_CHARS[match];
|
||||||
});
|
}) : str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -774,15 +882,33 @@ class Utils {
|
|||||||
""": '"',
|
""": '"',
|
||||||
"'": "'",
|
"'": "'",
|
||||||
"/": "/",
|
"/": "/",
|
||||||
"`": "`"
|
"`": "`",
|
||||||
|
"\ue000": "\u0000"
|
||||||
};
|
};
|
||||||
|
|
||||||
return str.replace(/&#?x?[a-z0-9]{2,4};/ig, function (match) {
|
return str.replace(/(&#?x?[a-z0-9]{2,4};|\ue000)/ig, function (match) {
|
||||||
return HTML_CHARS[match] || match;
|
return HTML_CHARS[match] || match;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
*
|
*
|
||||||
@ -1206,6 +1332,30 @@ class Utils {
|
|||||||
}[token];
|
}[token];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterate object in chunks of given size.
|
||||||
|
*
|
||||||
|
* @param {Iterable} iterable
|
||||||
|
* @param {number} chunksize
|
||||||
|
*/
|
||||||
|
static* chunked(iterable, chunksize) {
|
||||||
|
const iterator = iterable[Symbol.iterator]();
|
||||||
|
while (true) {
|
||||||
|
const res = [];
|
||||||
|
for (let i = 0; i < chunksize; i++) {
|
||||||
|
const next = iterator.next();
|
||||||
|
if (next.done) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
res.push(next.value);
|
||||||
|
}
|
||||||
|
if (res.length) {
|
||||||
|
yield res;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
100
src/core/config/Categories.json
Executable file → Normal file
100
src/core/config/Categories.json
Executable file → Normal file
@ -14,12 +14,16 @@
|
|||||||
"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",
|
||||||
"From Octal",
|
"From Octal",
|
||||||
"To Base32",
|
"To Base32",
|
||||||
"From Base32",
|
"From Base32",
|
||||||
|
"To Base45",
|
||||||
|
"From Base45",
|
||||||
"To Base58",
|
"To Base58",
|
||||||
"From Base58",
|
"From Base58",
|
||||||
"To Base62",
|
"To Base62",
|
||||||
@ -27,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",
|
||||||
@ -44,6 +50,8 @@
|
|||||||
"From Quoted Printable",
|
"From Quoted Printable",
|
||||||
"To Punycode",
|
"To Punycode",
|
||||||
"From Punycode",
|
"From Punycode",
|
||||||
|
"AMF Encode",
|
||||||
|
"AMF Decode",
|
||||||
"To Hex Content",
|
"To Hex Content",
|
||||||
"From Hex Content",
|
"From Hex Content",
|
||||||
"PEM to Hex",
|
"PEM to Hex",
|
||||||
@ -63,7 +71,10 @@
|
|||||||
"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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -77,22 +88,44 @@
|
|||||||
"DES Decrypt",
|
"DES Decrypt",
|
||||||
"Triple DES Encrypt",
|
"Triple DES Encrypt",
|
||||||
"Triple DES Decrypt",
|
"Triple DES Decrypt",
|
||||||
|
"Fernet Encrypt",
|
||||||
|
"Fernet Decrypt",
|
||||||
|
"LS47 Encrypt",
|
||||||
|
"LS47 Decrypt",
|
||||||
"RC2 Encrypt",
|
"RC2 Encrypt",
|
||||||
"RC2 Decrypt",
|
"RC2 Decrypt",
|
||||||
"RC4",
|
"RC4",
|
||||||
"RC4 Drop",
|
"RC4 Drop",
|
||||||
|
"ChaCha",
|
||||||
|
"Salsa20",
|
||||||
|
"XSalsa20",
|
||||||
|
"Rabbit",
|
||||||
|
"SM4 Encrypt",
|
||||||
|
"SM4 Decrypt",
|
||||||
|
"GOST Encrypt",
|
||||||
|
"GOST Decrypt",
|
||||||
|
"GOST Sign",
|
||||||
|
"GOST Verify",
|
||||||
|
"GOST Key Wrap",
|
||||||
|
"GOST Key Unwrap",
|
||||||
"ROT13",
|
"ROT13",
|
||||||
|
"ROT13 Brute Force",
|
||||||
"ROT47",
|
"ROT47",
|
||||||
|
"ROT47 Brute Force",
|
||||||
|
"ROT8000",
|
||||||
"XOR",
|
"XOR",
|
||||||
"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",
|
||||||
"Bacon Cipher Decode",
|
"Bacon Cipher Decode",
|
||||||
"Bifid Cipher Encode",
|
"Bifid Cipher Encode",
|
||||||
"Bifid Cipher Decode",
|
"Bifid Cipher Decode",
|
||||||
|
"Caesar Box Cipher",
|
||||||
"Affine Cipher Encode",
|
"Affine Cipher Encode",
|
||||||
"Affine Cipher Decode",
|
"Affine Cipher Decode",
|
||||||
"A1Z26 Cipher Encode",
|
"A1Z26 Cipher Encode",
|
||||||
@ -102,9 +135,12 @@
|
|||||||
"Atbash Cipher",
|
"Atbash Cipher",
|
||||||
"CipherSaber2 Encrypt",
|
"CipherSaber2 Encrypt",
|
||||||
"CipherSaber2 Decrypt",
|
"CipherSaber2 Decrypt",
|
||||||
|
"Cetacean Cipher Encode",
|
||||||
|
"Cetacean Cipher Decode",
|
||||||
"Substitute",
|
"Substitute",
|
||||||
"Derive PBKDF2 key",
|
"Derive PBKDF2 key",
|
||||||
"Derive EVP key",
|
"Derive EVP key",
|
||||||
|
"Derive HKDF key",
|
||||||
"Bcrypt",
|
"Bcrypt",
|
||||||
"Scrypt",
|
"Scrypt",
|
||||||
"JWT Sign",
|
"JWT Sign",
|
||||||
@ -112,13 +148,16 @@
|
|||||||
"JWT Decode",
|
"JWT Decode",
|
||||||
"Citrix CTX1 Encode",
|
"Citrix CTX1 Encode",
|
||||||
"Citrix CTX1 Decode",
|
"Citrix CTX1 Decode",
|
||||||
|
"AES Key Wrap",
|
||||||
|
"AES Key Unwrap",
|
||||||
"Pseudo-Random Number Generator",
|
"Pseudo-Random Number Generator",
|
||||||
"Enigma",
|
"Enigma",
|
||||||
"Bombe",
|
"Bombe",
|
||||||
"Multiple Bombe",
|
"Multiple Bombe",
|
||||||
"Typex",
|
"Typex",
|
||||||
"Lorenz",
|
"Lorenz",
|
||||||
"Colossus"
|
"Colossus",
|
||||||
|
"SIGABA"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -130,6 +169,8 @@
|
|||||||
"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",
|
||||||
@ -141,7 +182,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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -171,7 +219,8 @@
|
|||||||
"Bit shift right",
|
"Bit shift right",
|
||||||
"Rotate left",
|
"Rotate left",
|
||||||
"Rotate right",
|
"Rotate right",
|
||||||
"ROT13"
|
"ROT13",
|
||||||
|
"ROT8000"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -185,20 +234,29 @@
|
|||||||
"Parse IP range",
|
"Parse IP range",
|
||||||
"Parse IPv6 address",
|
"Parse IPv6 address",
|
||||||
"Parse IPv4 header",
|
"Parse IPv4 header",
|
||||||
|
"Parse TCP",
|
||||||
"Parse UDP",
|
"Parse UDP",
|
||||||
"Parse SSH Host Key",
|
"Parse SSH Host Key",
|
||||||
"Parse URI",
|
"Parse URI",
|
||||||
"URL Encode",
|
"URL Encode",
|
||||||
"URL Decode",
|
"URL Decode",
|
||||||
"Protobuf Decode",
|
"Protobuf Decode",
|
||||||
|
"Protobuf Encode",
|
||||||
"VarInt Encode",
|
"VarInt Encode",
|
||||||
"VarInt Decode",
|
"VarInt Decode",
|
||||||
|
"JA3 Fingerprint",
|
||||||
|
"JA3S Fingerprint",
|
||||||
|
"JA4 Fingerprint",
|
||||||
|
"JA4Server Fingerprint",
|
||||||
|
"HASSH Client Fingerprint",
|
||||||
|
"HASSH Server Fingerprint",
|
||||||
"Format MAC addresses",
|
"Format MAC addresses",
|
||||||
"Change IP format",
|
"Change IP format",
|
||||||
"Group IP addresses",
|
"Group IP addresses",
|
||||||
"Encode NetBIOS Name",
|
"Encode NetBIOS Name",
|
||||||
"Decode NetBIOS Name",
|
"Decode NetBIOS Name",
|
||||||
"Defang URL",
|
"Defang URL",
|
||||||
|
"Fang URL",
|
||||||
"Defang IP Addresses"
|
"Defang IP Addresses"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -221,13 +279,16 @@
|
|||||||
"Remove null bytes",
|
"Remove null bytes",
|
||||||
"To Upper case",
|
"To Upper case",
|
||||||
"To Lower case",
|
"To Lower case",
|
||||||
|
"Swap case",
|
||||||
"To Case Insensitive Regex",
|
"To Case Insensitive Regex",
|
||||||
"From Case Insensitive Regex",
|
"From Case Insensitive Regex",
|
||||||
"Add line numbers",
|
"Add line numbers",
|
||||||
"Remove line numbers",
|
"Remove line numbers",
|
||||||
|
"Get All Casings",
|
||||||
"To Table",
|
"To Table",
|
||||||
"Reverse",
|
"Reverse",
|
||||||
"Sort",
|
"Sort",
|
||||||
|
"Shuffle",
|
||||||
"Unique",
|
"Unique",
|
||||||
"Split",
|
"Split",
|
||||||
"Filter",
|
"Filter",
|
||||||
@ -243,6 +304,7 @@
|
|||||||
"Fuzzy Match",
|
"Fuzzy Match",
|
||||||
"Offset checker",
|
"Offset checker",
|
||||||
"Hamming Distance",
|
"Hamming Distance",
|
||||||
|
"Levenshtein Distance",
|
||||||
"Convert distance",
|
"Convert distance",
|
||||||
"Convert area",
|
"Convert area",
|
||||||
"Convert mass",
|
"Convert mass",
|
||||||
@ -257,7 +319,8 @@
|
|||||||
"Escape string",
|
"Escape string",
|
||||||
"Unescape string",
|
"Unescape string",
|
||||||
"Pseudo-Random Number Generator",
|
"Pseudo-Random Number Generator",
|
||||||
"Sleep"
|
"Sleep",
|
||||||
|
"File Tree"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -269,6 +332,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"
|
||||||
@ -285,13 +349,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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -308,7 +374,14 @@
|
|||||||
"Bzip2 Decompress",
|
"Bzip2 Decompress",
|
||||||
"Bzip2 Compress",
|
"Bzip2 Compress",
|
||||||
"Tar",
|
"Tar",
|
||||||
"Untar"
|
"Untar",
|
||||||
|
"LZString Decompress",
|
||||||
|
"LZString Compress",
|
||||||
|
"LZMA Decompress",
|
||||||
|
"LZMA Compress",
|
||||||
|
"LZ4 Decompress",
|
||||||
|
"LZ4 Compress",
|
||||||
|
"LZNT1 Decompress"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -333,17 +406,23 @@
|
|||||||
"Snefru",
|
"Snefru",
|
||||||
"BLAKE2b",
|
"BLAKE2b",
|
||||||
"BLAKE2s",
|
"BLAKE2s",
|
||||||
"GOST hash",
|
"GOST Hash",
|
||||||
"Streebog",
|
"Streebog",
|
||||||
"SSDEEP",
|
"SSDEEP",
|
||||||
"CTPH",
|
"CTPH",
|
||||||
"Compare SSDEEP hashes",
|
"Compare SSDEEP hashes",
|
||||||
"Compare CTPH hashes",
|
"Compare CTPH hashes",
|
||||||
"HMAC",
|
"HMAC",
|
||||||
|
"CMAC",
|
||||||
"Bcrypt",
|
"Bcrypt",
|
||||||
"Bcrypt compare",
|
"Bcrypt compare",
|
||||||
"Bcrypt parse",
|
"Bcrypt parse",
|
||||||
|
"Argon2",
|
||||||
|
"Argon2 compare",
|
||||||
"Scrypt",
|
"Scrypt",
|
||||||
|
"NT Hash",
|
||||||
|
"LM Hash",
|
||||||
|
"MurmurHash3",
|
||||||
"Fletcher-8 Checksum",
|
"Fletcher-8 Checksum",
|
||||||
"Fletcher-16 Checksum",
|
"Fletcher-16 Checksum",
|
||||||
"Fletcher-32 Checksum",
|
"Fletcher-32 Checksum",
|
||||||
@ -401,7 +480,8 @@
|
|||||||
"Extract RGBA",
|
"Extract RGBA",
|
||||||
"View Bit Plane",
|
"View Bit Plane",
|
||||||
"Randomize Colour Palette",
|
"Randomize Colour Palette",
|
||||||
"Extract LSB"
|
"Extract LSB",
|
||||||
|
"ELF Info"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -444,8 +524,10 @@
|
|||||||
"Frequency distribution",
|
"Frequency distribution",
|
||||||
"Index of Coincidence",
|
"Index of Coincidence",
|
||||||
"Chi Square",
|
"Chi Square",
|
||||||
|
"P-list Viewer",
|
||||||
"Disassemble x86",
|
"Disassemble x86",
|
||||||
"Pseudo-Random Number Generator",
|
"Pseudo-Random Number Generator",
|
||||||
|
"Generate De Bruijn Sequence",
|
||||||
"Generate UUID",
|
"Generate UUID",
|
||||||
"Generate TOTP",
|
"Generate TOTP",
|
||||||
"Generate HOTP",
|
"Generate HOTP",
|
||||||
|
@ -30,12 +30,12 @@ fs.readdirSync(path.join(dir, "../operations")).forEach(file => {
|
|||||||
|
|
||||||
// Construct index file
|
// Construct index file
|
||||||
let code = `/**
|
let code = `/**
|
||||||
* THIS FILE IS AUTOMATICALLY GENERATED BY src/core/config/scripts/generateOpsIndex.mjs
|
* THIS FILE IS AUTOMATICALLY GENERATED BY src/core/config/scripts/generateOpsIndex.mjs
|
||||||
*
|
*
|
||||||
* @author n1474335 [n1474335@gmail.com]
|
* @author n1474335 [n1474335@gmail.com]
|
||||||
* @copyright Crown Copyright ${new Date().getUTCFullYear()}
|
* @copyright Crown Copyright ${new Date().getUTCFullYear()}
|
||||||
* @license Apache-2.0
|
* @license Apache-2.0
|
||||||
*/
|
*/
|
||||||
`;
|
`;
|
||||||
|
|
||||||
opObjs.forEach(obj => {
|
opObjs.forEach(obj => {
|
||||||
|
144
src/core/config/scripts/newMinorVersion.mjs
Normal file
144
src/core/config/scripts/newMinorVersion.mjs
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
/**
|
||||||
|
* This script updates the CHANGELOG when a new minor version is created.
|
||||||
|
*
|
||||||
|
* @author n1474335 [n1474335@gmail.com]
|
||||||
|
* @copyright Crown Copyright 2022
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint no-console: ["off"] */
|
||||||
|
|
||||||
|
import prompt from "prompt";
|
||||||
|
import colors from "colors";
|
||||||
|
import path from "path";
|
||||||
|
import fs from "fs";
|
||||||
|
import process from "process";
|
||||||
|
|
||||||
|
const dir = path.join(process.cwd() + "/src/core/config/");
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
console.log("\nCWD: " + process.cwd());
|
||||||
|
console.log("Error: newMinorVersion.mjs should be run from the project root");
|
||||||
|
console.log("Example> node --experimental-modules src/core/config/scripts/newMinorVersion.mjs");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let changelogData = fs.readFileSync(path.join(process.cwd(), "CHANGELOG.md"), "utf8");
|
||||||
|
const lastVersion = changelogData.match(/## Details\s+### \[(\d+)\.(\d+)\.(\d+)\]/);
|
||||||
|
const newVersion = [
|
||||||
|
parseInt(lastVersion[1], 10),
|
||||||
|
parseInt(lastVersion[2], 10) + 1,
|
||||||
|
0
|
||||||
|
];
|
||||||
|
|
||||||
|
let knownContributors = changelogData.match(/^\[@([^\]]+)\]/gm);
|
||||||
|
knownContributors = knownContributors.map(c => c.slice(2, -1));
|
||||||
|
|
||||||
|
const date = (new Date()).toISOString().split("T")[0];
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
properties: {
|
||||||
|
message: {
|
||||||
|
description: "A short but descriptive summary of a feature in this version",
|
||||||
|
example: "Added 'Op name' operation",
|
||||||
|
prompt: "Feature description",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
author: {
|
||||||
|
description: "The author of the feature (only one supported, edit manually to add more)",
|
||||||
|
example: "n1474335",
|
||||||
|
prompt: "Author",
|
||||||
|
type: "string",
|
||||||
|
default: "n1474335"
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
description: "The PR number or full commit hash for this feature.",
|
||||||
|
example: "1200",
|
||||||
|
prompt: "Pull request or commit ID",
|
||||||
|
type: "string"
|
||||||
|
},
|
||||||
|
another: {
|
||||||
|
description: "y/n",
|
||||||
|
example: "y",
|
||||||
|
prompt: "Add another feature?",
|
||||||
|
type: "string",
|
||||||
|
pattern: /^[yn]$/,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build schema
|
||||||
|
for (const prop in schema.properties) {
|
||||||
|
const p = schema.properties[prop];
|
||||||
|
p.description = "\n" + colors.white(p.description) + colors.cyan("\nExample: " + p.example) + "\n" + colors.green(p.prompt);
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt.message = "";
|
||||||
|
prompt.delimiter = ":".green;
|
||||||
|
|
||||||
|
const features = [];
|
||||||
|
const authors = [];
|
||||||
|
const prIDs = [];
|
||||||
|
const commitIDs = [];
|
||||||
|
|
||||||
|
prompt.start();
|
||||||
|
|
||||||
|
const getFeature = function() {
|
||||||
|
prompt.get(schema, (err, result) => {
|
||||||
|
if (err) {
|
||||||
|
console.log("\nExiting script.");
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
features.push(result);
|
||||||
|
|
||||||
|
if (result.another === "y") {
|
||||||
|
getFeature();
|
||||||
|
} else {
|
||||||
|
let message = `### [${newVersion[0]}.${newVersion[1]}.${newVersion[2]}] - ${date}\n`;
|
||||||
|
|
||||||
|
features.forEach(feature => {
|
||||||
|
const id = feature.id.length > 10 ? feature.id.slice(0, 7) : "#" + feature.id;
|
||||||
|
message += `- ${feature.message} [@${feature.author}] | [${id}]\n`;
|
||||||
|
|
||||||
|
if (!knownContributors.includes(feature.author)) {
|
||||||
|
authors.push(`[@${feature.author}]: https://github.com/${feature.author}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (feature.id.length > 10) {
|
||||||
|
commitIDs.push(`[${id}]: https://github.com/gchq/CyberChef/commit/${feature.id}`);
|
||||||
|
} else {
|
||||||
|
prIDs.push(`[#${feature.id}]: https://github.com/gchq/CyberChef/pull/${feature.id}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Message
|
||||||
|
changelogData = changelogData.replace(/## Details\n\n/, "## Details\n\n" + message + "\n");
|
||||||
|
|
||||||
|
// Tag
|
||||||
|
const newTag = `[${newVersion[0]}.${newVersion[1]}.${newVersion[2]}]: https://github.com/gchq/CyberChef/releases/tag/v${newVersion[0]}.${newVersion[1]}.${newVersion[2]}\n`;
|
||||||
|
changelogData = changelogData.replace(/\n\n(\[\d+\.\d+\.\d+\]: https)/, "\n\n" + newTag + "$1");
|
||||||
|
|
||||||
|
// Author
|
||||||
|
authors.forEach(author => {
|
||||||
|
changelogData = changelogData.replace(/(\n\[@[^\]]+\]: https:\/\/github\.com\/[^\n]+\n)\n/, "$1" + author + "\n\n");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Commit IDs
|
||||||
|
commitIDs.forEach(commitID => {
|
||||||
|
changelogData = changelogData.replace(/(\n\[[^\].]+\]: https:\/\/github.com\/gchq\/CyberChef\/commit\/[^\n]+\n)\n/, "$1" + commitID + "\n\n");
|
||||||
|
});
|
||||||
|
|
||||||
|
// PR IDs
|
||||||
|
prIDs.forEach(prID => {
|
||||||
|
changelogData = changelogData.replace(/(\n\[#[^\]]+\]: https:\/\/github.com\/gchq\/CyberChef\/pull\/[^\n]+\n)\n*$/, "$1" + prID + "\n\n");
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.writeFileSync(path.join(process.cwd(), "CHANGELOG.md"), changelogData);
|
||||||
|
|
||||||
|
console.log("Written CHANGELOG.md\nCommit changes and then run `npm version minor`.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
getFeature();
|
@ -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 = [
|
||||||
|
@ -24,12 +24,11 @@ class DishBigNumber extends DishType {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* convert the given value from a ArrayBuffer
|
* convert the given value from a ArrayBuffer
|
||||||
* @param {boolean} notUTF8
|
|
||||||
*/
|
*/
|
||||||
static fromArrayBuffer(notUTF8) {
|
static fromArrayBuffer() {
|
||||||
DishBigNumber.checkForValue(this.value);
|
DishBigNumber.checkForValue(this.value);
|
||||||
try {
|
try {
|
||||||
this.value = new BigNumber(Utils.arrayBufferToStr(this.value, !notUTF8));
|
this.value = new BigNumber(Utils.arrayBufferToStr(this.value));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.value = new BigNumber(NaN);
|
this.value = new BigNumber(NaN);
|
||||||
}
|
}
|
||||||
|
@ -22,11 +22,10 @@ class DishJSON extends DishType {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* convert the given value from a ArrayBuffer
|
* convert the given value from a ArrayBuffer
|
||||||
* @param {boolean} notUTF8
|
|
||||||
*/
|
*/
|
||||||
static fromArrayBuffer(notUTF8) {
|
static fromArrayBuffer() {
|
||||||
DishJSON.checkForValue(this.value);
|
DishJSON.checkForValue(this.value);
|
||||||
this.value = JSON.parse(Utils.arrayBufferToStr(this.value, !notUTF8));
|
this.value = JSON.parse(Utils.arrayBufferToStr(this.value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,11 +23,10 @@ class DishNumber extends DishType {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* convert the given value from a ArrayBuffer
|
* convert the given value from a ArrayBuffer
|
||||||
* @param {boolean} notUTF8
|
|
||||||
*/
|
*/
|
||||||
static fromArrayBuffer(notUTF8) {
|
static fromArrayBuffer() {
|
||||||
DishNumber.checkForValue(this.value);
|
DishNumber.checkForValue(this.value);
|
||||||
this.value = this.value ? parseFloat(Utils.arrayBufferToStr(this.value, !notUTF8)) : 0;
|
this.value = this.value ? parseFloat(Utils.arrayBufferToStr(this.value)) : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,11 +23,10 @@ class DishString extends DishType {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* convert the given value from a ArrayBuffer
|
* convert the given value from a ArrayBuffer
|
||||||
* @param {boolean} notUTF8
|
|
||||||
*/
|
*/
|
||||||
static fromArrayBuffer(notUTF8) {
|
static fromArrayBuffer() {
|
||||||
DishString.checkForValue(this.value);
|
DishString.checkForValue(this.value);
|
||||||
this.value = this.value ? Utils.arrayBufferToStr(this.value, !notUTF8) : "";
|
this.value = this.value ? Utils.arrayBufferToStr(this.value) : "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,9 +29,8 @@ class DishType {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* convert the given value from a ArrayBuffer
|
* convert the given value from a ArrayBuffer
|
||||||
* @param {boolean} notUTF8
|
|
||||||
*/
|
*/
|
||||||
static fromArrayBuffer(notUTF8=undefined) {
|
static fromArrayBuffer() {
|
||||||
throw new Error("fromArrayBuffer has not been implemented");
|
throw new Error("fromArrayBuffer has not been implemented");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import OperationError from "./OperationError.mjs";
|
import OperationError from "./OperationError.mjs";
|
||||||
import DishError from "./DishError.mjs";
|
import DishError from "./DishError.mjs";
|
||||||
import ExcludedOperationError from "./ExcludedOperationError";
|
import ExcludedOperationError from "./ExcludedOperationError.mjs";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
OperationError,
|
OperationError,
|
||||||
|
27
src/core/lib/Base45.mjs
Normal file
27
src/core/lib/Base45.mjs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Base45 resources.
|
||||||
|
*
|
||||||
|
* @author Thomas Weißschuh [thomas@t-8ch.de]
|
||||||
|
* @copyright Crown Copyright 2021
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highlight to Base45
|
||||||
|
*/
|
||||||
|
export function highlightToBase45(pos, args) {
|
||||||
|
pos[0].start = Math.floor(pos[0].start / 2) * 3;
|
||||||
|
pos[0].end = Math.ceil(pos[0].end / 2) * 3;
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highlight from Base45
|
||||||
|
*/
|
||||||
|
export function highlightFromBase45(pos, args) {
|
||||||
|
pos[0].start = Math.floor(pos[0].start / 3) * 2;
|
||||||
|
pos[0].end = Math.ceil(pos[0].end / 3) * 2;
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ALPHABET = "0-9A-Z $%*+\\-./:";
|
@ -25,12 +25,12 @@ import OperationError from "../errors/OperationError.mjs";
|
|||||||
*/
|
*/
|
||||||
export function toBase64(data, alphabet="A-Za-z0-9+/=") {
|
export function toBase64(data, alphabet="A-Za-z0-9+/=") {
|
||||||
if (!data) return "";
|
if (!data) return "";
|
||||||
|
if (typeof data == "string") {
|
||||||
|
data = Utils.strToArrayBuffer(data);
|
||||||
|
}
|
||||||
if (data instanceof ArrayBuffer) {
|
if (data instanceof ArrayBuffer) {
|
||||||
data = new Uint8Array(data);
|
data = new Uint8Array(data);
|
||||||
}
|
}
|
||||||
if (typeof data == "string") {
|
|
||||||
data = Utils.strToByteArray(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
alphabet = Utils.expandAlphRange(alphabet).join("");
|
alphabet = Utils.expandAlphRange(alphabet).join("");
|
||||||
if (alphabet.length !== 64 && alphabet.length !== 65) { // Allow for padding
|
if (alphabet.length !== 64 && alphabet.length !== 65) { // Allow for padding
|
||||||
@ -82,15 +82,46 @@ export function toBase64(data, alphabet="A-Za-z0-9+/=") {
|
|||||||
* // returns [72, 101, 108, 108, 111]
|
* // returns [72, 101, 108, 108, 111]
|
||||||
* fromBase64("SGVsbG8=", null, "byteArray");
|
* fromBase64("SGVsbG8=", null, "byteArray");
|
||||||
*/
|
*/
|
||||||
export function fromBase64(data, alphabet="A-Za-z0-9+/=", returnType="string", removeNonAlphChars=true) {
|
export function fromBase64(data, alphabet="A-Za-z0-9+/=", returnType="string", removeNonAlphChars=true, strictMode=false) {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return returnType === "string" ? "" : [];
|
return returnType === "string" ? "" : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
alphabet = alphabet || "A-Za-z0-9+/=";
|
alphabet = alphabet || "A-Za-z0-9+/=";
|
||||||
alphabet = Utils.expandAlphRange(alphabet).join("");
|
alphabet = Utils.expandAlphRange(alphabet).join("");
|
||||||
|
|
||||||
|
// Confirm alphabet is a valid length
|
||||||
if (alphabet.length !== 64 && alphabet.length !== 65) { // Allow for padding
|
if (alphabet.length !== 64 && alphabet.length !== 65) { // Allow for padding
|
||||||
throw new OperationError(`Invalid Base64 alphabet length (${alphabet.length}): ${alphabet}`);
|
throw new OperationError(`Error: Base64 alphabet should be 64 characters long, or 65 with a padding character. Found ${alphabet.length}: ${alphabet}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove non-alphabet characters
|
||||||
|
if (removeNonAlphChars) {
|
||||||
|
const re = new RegExp("[^" + alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g");
|
||||||
|
data = data.replace(re, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strictMode) {
|
||||||
|
// Check for incorrect lengths (even without padding)
|
||||||
|
if (data.length % 4 === 1) {
|
||||||
|
throw new OperationError(`Error: Invalid Base64 input length (${data.length}). Cannot be 4n+1, even without padding chars.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alphabet.length === 65) { // Padding character included
|
||||||
|
const pad = alphabet.charAt(64);
|
||||||
|
const padPos = data.indexOf(pad);
|
||||||
|
if (padPos >= 0) {
|
||||||
|
// Check that the padding character is only used at the end and maximum of twice
|
||||||
|
if (padPos < data.length - 2 || data.charAt(data.length - 1) !== pad) {
|
||||||
|
throw new OperationError(`Error: Base64 padding character (${pad}) not used in the correct place.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that input is padded to the correct length
|
||||||
|
if (data.length % 4 !== 0) {
|
||||||
|
throw new OperationError("Error: Base64 not padded to a multiple of 4.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const output = [];
|
const output = [];
|
||||||
@ -98,31 +129,28 @@ export function fromBase64(data, alphabet="A-Za-z0-9+/=", returnType="string", r
|
|||||||
enc1, enc2, enc3, enc4,
|
enc1, enc2, enc3, enc4,
|
||||||
i = 0;
|
i = 0;
|
||||||
|
|
||||||
if (removeNonAlphChars) {
|
|
||||||
const re = new RegExp("[^" + alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g");
|
|
||||||
data = data.replace(re, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
while (i < data.length) {
|
while (i < data.length) {
|
||||||
enc1 = alphabet.indexOf(data.charAt(i++));
|
// Including `|| null` forces empty strings to null so that indexOf returns -1 instead of 0
|
||||||
enc2 = alphabet.indexOf(data.charAt(i++) || "=");
|
enc1 = alphabet.indexOf(data.charAt(i++) || null);
|
||||||
enc3 = alphabet.indexOf(data.charAt(i++) || "=");
|
enc2 = alphabet.indexOf(data.charAt(i++) || null);
|
||||||
enc4 = alphabet.indexOf(data.charAt(i++) || "=");
|
enc3 = alphabet.indexOf(data.charAt(i++) || null);
|
||||||
|
enc4 = alphabet.indexOf(data.charAt(i++) || null);
|
||||||
|
|
||||||
enc2 = enc2 === -1 ? 64 : enc2;
|
if (strictMode && (enc1 < 0 || enc2 < 0 || enc3 < 0 || enc4 < 0)) {
|
||||||
enc3 = enc3 === -1 ? 64 : enc3;
|
throw new OperationError("Error: Base64 input contains non-alphabet char(s)");
|
||||||
enc4 = enc4 === -1 ? 64 : enc4;
|
}
|
||||||
|
|
||||||
chr1 = (enc1 << 2) | (enc2 >> 4);
|
chr1 = (enc1 << 2) | (enc2 >> 4);
|
||||||
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
|
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
|
||||||
chr3 = ((enc3 & 3) << 6) | enc4;
|
chr3 = ((enc3 & 3) << 6) | enc4;
|
||||||
|
|
||||||
output.push(chr1);
|
if (chr1 >= 0 && chr1 < 256) {
|
||||||
|
output.push(chr1);
|
||||||
if (enc3 !== 64) {
|
}
|
||||||
|
if (chr2 >= 0 && chr2 < 256 && enc3 !== 64) {
|
||||||
output.push(chr2);
|
output.push(chr2);
|
||||||
}
|
}
|
||||||
if (enc4 !== 64) {
|
if (chr3 >= 0 && chr3 < 256 && enc4 !== 64) {
|
||||||
output.push(chr3);
|
output.push(chr3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base85 resources.
|
* Base85 resources.
|
||||||
*
|
*
|
||||||
@ -32,13 +34,12 @@ export const ALPHABET_OPTIONS = [
|
|||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function alphabetName(alphabet) {
|
export function alphabetName(alphabet) {
|
||||||
alphabet = alphabet.replace(/'/g, "'");
|
alphabet = escape(alphabet);
|
||||||
alphabet = alphabet.replace(/"/g, """);
|
|
||||||
alphabet = alphabet.replace(/\\/g, "\");
|
|
||||||
let name;
|
let name;
|
||||||
|
|
||||||
ALPHABET_OPTIONS.forEach(function(a) {
|
ALPHABET_OPTIONS.forEach(function(a) {
|
||||||
if (escape(alphabet) === escape(a.value)) name = a.name;
|
const expanded = Utils.expandAlphRange(a.value).join("");
|
||||||
|
if (alphabet === escape(expanded)) name = a.name;
|
||||||
});
|
});
|
||||||
|
|
||||||
return name;
|
return name;
|
||||||
|
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`);
|
||||||
|
}
|
||||||
|
|
@ -13,33 +13,39 @@ import OperationError from "../errors/OperationError.mjs";
|
|||||||
/**
|
/**
|
||||||
* Convert a byte array into a binary string.
|
* Convert a byte array into a binary string.
|
||||||
*
|
*
|
||||||
* @param {Uint8Array|byteArray} data
|
* @param {Uint8Array|byteArray|number} data
|
||||||
* @param {string} [delim="Space"]
|
* @param {string} [delim="Space"]
|
||||||
* @param {number} [padding=8]
|
* @param {number} [padding=8]
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* // returns "00010000 00100000 00110000"
|
* // returns "00001010 00010100 00011110"
|
||||||
* toBinary([10,20,30]);
|
* toBinary([10,20,30]);
|
||||||
*
|
*
|
||||||
* // returns "00010000 00100000 00110000"
|
* // returns "00001010:00010100:00011110"
|
||||||
* toBinary([10,20,30], ":");
|
* toBinary([10,20,30], "Colon");
|
||||||
|
*
|
||||||
|
* // returns "1010:10100:11110"
|
||||||
|
* toBinary([10,20,30], "Colon", 0);
|
||||||
*/
|
*/
|
||||||
export function toBinary(data, delim="Space", padding=8) {
|
export function toBinary(data, delim="Space", padding=8) {
|
||||||
if (!data) return "";
|
if (data === undefined || data === null)
|
||||||
|
throw new OperationError("Unable to convert to binary: Empty input data enocuntered");
|
||||||
|
|
||||||
delim = Utils.charRep(delim);
|
delim = Utils.charRep(delim);
|
||||||
let output = "";
|
let output = "";
|
||||||
|
|
||||||
for (let i = 0; i < data.length; i++) {
|
if (data.length) { // array
|
||||||
output += data[i].toString(2).padStart(padding, "0") + delim;
|
for (let i = 0; i < data.length; i++) {
|
||||||
}
|
output += data[i].toString(2).padStart(padding, "0");
|
||||||
|
if (i !== data.length - 1) output += delim;
|
||||||
if (delim.length) {
|
}
|
||||||
return output.slice(0, -delim.length);
|
} else if (typeof data === "number") { // Single value
|
||||||
|
return data.toString(2).padStart(padding, "0");
|
||||||
} else {
|
} else {
|
||||||
return output;
|
return "";
|
||||||
}
|
}
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -53,10 +59,10 @@ export function toBinary(data, delim="Space", padding=8) {
|
|||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* // returns [10,20,30]
|
* // returns [10,20,30]
|
||||||
* fromBinary("00010000 00100000 00110000");
|
* fromBinary("00001010 00010100 00011110");
|
||||||
*
|
*
|
||||||
* // returns [10,20,30]
|
* // returns [10,20,30]
|
||||||
* fromBinary("00010000:00100000:00110000", "Colon");
|
* fromBinary("00001010:00010100:00011110", "Colon");
|
||||||
*/
|
*/
|
||||||
export function fromBinary(data, delim="Space", byteLen=8) {
|
export function fromBinary(data, delim="Space", byteLen=8) {
|
||||||
if (byteLen < 1 || Math.round(byteLen) !== byteLen)
|
if (byteLen < 1 || Math.round(byteLen) !== byteLen)
|
||||||
|
@ -34,10 +34,10 @@ export function bitOp (input, key, func, nullPreserving, scheme) {
|
|||||||
!(nullPreserving && (o === 0 || o === k))) {
|
!(nullPreserving && (o === 0 || o === k))) {
|
||||||
switch (scheme) {
|
switch (scheme) {
|
||||||
case "Input differential":
|
case "Input differential":
|
||||||
key[i % key.length] = x;
|
key[i % key.length] = o;
|
||||||
break;
|
break;
|
||||||
case "Output differential":
|
case "Output differential":
|
||||||
key[i % key.length] = o;
|
key[i % key.length] = x;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import OperationError from "../errors/OperationError.mjs";
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @constant
|
* @constant
|
||||||
@ -128,7 +129,7 @@ export function getScatterValuesWithColour(input, recordDelimiter, fieldDelimite
|
|||||||
if (Number.isNaN(x)) throw new OperationError("Values must be numbers in base 10.");
|
if (Number.isNaN(x)) throw new OperationError("Values must be numbers in base 10.");
|
||||||
if (Number.isNaN(y)) throw new OperationError("Values must be numbers in base 10.");
|
if (Number.isNaN(y)) throw new OperationError("Values must be numbers in base 10.");
|
||||||
|
|
||||||
return [x, y, colour];
|
return [x, y, Utils.escapeHtml(colour)];
|
||||||
});
|
});
|
||||||
|
|
||||||
return { headings, values };
|
return { headings, values };
|
||||||
|
@ -6,10 +6,12 @@
|
|||||||
* @license Apache-2.0
|
* @license Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import cptable from "codepage";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Character encoding format mappings.
|
* Character encoding format mappings.
|
||||||
*/
|
*/
|
||||||
export const IO_FORMAT = {
|
export const CHR_ENC_CODE_PAGES = {
|
||||||
"UTF-8 (65001)": 65001,
|
"UTF-8 (65001)": 65001,
|
||||||
"UTF-7 (65000)": 65000,
|
"UTF-7 (65000)": 65000,
|
||||||
"UTF-16LE (1200)": 1200,
|
"UTF-16LE (1200)": 1200,
|
||||||
@ -164,6 +166,57 @@ export const IO_FORMAT = {
|
|||||||
"Simplified Chinese GB18030 (54936)": 54936,
|
"Simplified Chinese GB18030 (54936)": 54936,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const CHR_ENC_SIMPLE_LOOKUP = {};
|
||||||
|
export const CHR_ENC_SIMPLE_REVERSE_LOOKUP = {};
|
||||||
|
|
||||||
|
for (const name in CHR_ENC_CODE_PAGES) {
|
||||||
|
const simpleName = name.match(/(^.+)\([\d/]+\)$/)[1];
|
||||||
|
|
||||||
|
CHR_ENC_SIMPLE_LOOKUP[simpleName] = CHR_ENC_CODE_PAGES[name];
|
||||||
|
CHR_ENC_SIMPLE_REVERSE_LOOKUP[CHR_ENC_CODE_PAGES[name]] = simpleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the width of the character set for the given codepage.
|
||||||
|
* For example, UTF-8 is a Single Byte Character Set, whereas
|
||||||
|
* UTF-16 is a Double Byte Character Set.
|
||||||
|
*
|
||||||
|
* @param {number} page - The codepage number
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
export function chrEncWidth(page) {
|
||||||
|
if (typeof page !== "number") return 0;
|
||||||
|
|
||||||
|
// Raw Bytes have a width of 1
|
||||||
|
if (page === 0) return 1;
|
||||||
|
|
||||||
|
const pageStr = page.toString();
|
||||||
|
// Confirm this page is legitimate
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(CHR_ENC_SIMPLE_REVERSE_LOOKUP, pageStr))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// Statically defined code pages
|
||||||
|
if (Object.prototype.hasOwnProperty.call(cptable, pageStr))
|
||||||
|
return cptable[pageStr].dec.length > 256 ? 2 : 1;
|
||||||
|
|
||||||
|
// Cached code pages
|
||||||
|
if (cptable.utils.cache.sbcs.includes(pageStr))
|
||||||
|
return 1;
|
||||||
|
if (cptable.utils.cache.dbcs.includes(pageStr))
|
||||||
|
return 2;
|
||||||
|
|
||||||
|
// Dynamically generated code pages
|
||||||
|
if (Object.prototype.hasOwnProperty.call(cptable.utils.magic, pageStr)) {
|
||||||
|
// Generate a single character and measure it
|
||||||
|
const a = cptable.utils.encode(page, "a");
|
||||||
|
return a.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unicode Normalisation Forms
|
* Unicode Normalisation Forms
|
||||||
*
|
*
|
||||||
@ -171,8 +224,85 @@ export const IO_FORMAT = {
|
|||||||
* @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)
|
||||||
|
@ -12,15 +12,15 @@
|
|||||||
*
|
*
|
||||||
* @param {string} input
|
* @param {string} input
|
||||||
* @param {RegExp} searchRegex
|
* @param {RegExp} searchRegex
|
||||||
* @param {RegExp} removeRegex - A regular expression defining results to remove from the
|
* @param {RegExp} [removeRegex=null] - A regular expression defining results to remove from the
|
||||||
* final list
|
* final list
|
||||||
* @param {boolean} includeTotal - Whether or not to include the total number of results
|
* @param {Function} [sortBy=null] - The sorting comparison function to apply
|
||||||
|
* @param {boolean} [unique=false] - Whether to unique the results
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function search (input, searchRegex, removeRegex, includeTotal) {
|
export function search(input, searchRegex, removeRegex=null, sortBy=null, unique=false) {
|
||||||
let output = "",
|
let results = [];
|
||||||
total = 0,
|
let match;
|
||||||
match;
|
|
||||||
|
|
||||||
while ((match = searchRegex.exec(input))) {
|
while ((match = searchRegex.exec(input))) {
|
||||||
// Moves pointer when an empty string is matched (prevents infinite loop)
|
// Moves pointer when an empty string is matched (prevents infinite loop)
|
||||||
@ -30,14 +30,19 @@ export function search (input, searchRegex, removeRegex, includeTotal) {
|
|||||||
|
|
||||||
if (removeRegex && removeRegex.test(match[0]))
|
if (removeRegex && removeRegex.test(match[0]))
|
||||||
continue;
|
continue;
|
||||||
total++;
|
|
||||||
output += match[0] + "\n";
|
results.push(match[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (includeTotal)
|
if (sortBy) {
|
||||||
output = "Total found: " + total + "\n\n" + output;
|
results = results.sort(sortBy);
|
||||||
|
}
|
||||||
|
|
||||||
return output;
|
if (unique) {
|
||||||
|
results = results.unique();
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -70,6 +70,27 @@ export const FILE_SIGNATURES = {
|
|||||||
10: 0x42,
|
10: 0x42,
|
||||||
11: 0x50
|
11: 0x50
|
||||||
},
|
},
|
||||||
|
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
|
extractor: null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -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))
|
||||||
@ -3032,6 +3053,30 @@ export function extractPNG(bytes, offset) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WEBP extractor.
|
||||||
|
*
|
||||||
|
* @param {Uint8Array} bytes
|
||||||
|
* @param {number} offset
|
||||||
|
* @returns {Uint8Array}
|
||||||
|
*/
|
||||||
|
export function extractWEBP(bytes, offset) {
|
||||||
|
const stream = new Stream(bytes.slice(offset));
|
||||||
|
|
||||||
|
// Move to file size offset.
|
||||||
|
stream.moveForwardsBy(4);
|
||||||
|
|
||||||
|
// Read file size field.
|
||||||
|
const fileSize = stream.readInt(4, "le");
|
||||||
|
|
||||||
|
// Move to end of file.
|
||||||
|
// There is no need to minus 8 from the size as the size factors in the offset.
|
||||||
|
stream.moveForwardsBy(fileSize);
|
||||||
|
|
||||||
|
return stream.carve();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BMP extractor.
|
* BMP extractor.
|
||||||
*
|
*
|
||||||
@ -3778,8 +3823,8 @@ function parseDEFLATE(stream) {
|
|||||||
|
|
||||||
while (!finalBlock) {
|
while (!finalBlock) {
|
||||||
// Read header
|
// Read header
|
||||||
finalBlock = stream.readBits(1);
|
finalBlock = stream.readBits(1, "le");
|
||||||
const blockType = stream.readBits(2);
|
const blockType = stream.readBits(2, "le");
|
||||||
|
|
||||||
if (blockType === 0) {
|
if (blockType === 0) {
|
||||||
/* No compression */
|
/* No compression */
|
||||||
@ -3798,16 +3843,16 @@ function parseDEFLATE(stream) {
|
|||||||
/* Dynamic Huffman */
|
/* Dynamic Huffman */
|
||||||
|
|
||||||
// Read the number of liternal and length codes
|
// Read the number of liternal and length codes
|
||||||
const hlit = stream.readBits(5) + 257;
|
const hlit = stream.readBits(5, "le") + 257;
|
||||||
// Read the number of distance codes
|
// Read the number of distance codes
|
||||||
const hdist = stream.readBits(5) + 1;
|
const hdist = stream.readBits(5, "le") + 1;
|
||||||
// Read the number of code lengths
|
// Read the number of code lengths
|
||||||
const hclen = stream.readBits(4) + 4;
|
const hclen = stream.readBits(4, "le") + 4;
|
||||||
|
|
||||||
// Parse code lengths
|
// Parse code lengths
|
||||||
const codeLengths = new Uint8Array(huffmanOrder.length);
|
const codeLengths = new Uint8Array(huffmanOrder.length);
|
||||||
for (let i = 0; i < hclen; i++) {
|
for (let i = 0; i < hclen; i++) {
|
||||||
codeLengths[huffmanOrder[i]] = stream.readBits(3);
|
codeLengths[huffmanOrder[i]] = stream.readBits(3, "le");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse length table
|
// Parse length table
|
||||||
@ -3819,16 +3864,16 @@ function parseDEFLATE(stream) {
|
|||||||
code = readHuffmanCode(stream, codeLengthsTable);
|
code = readHuffmanCode(stream, codeLengthsTable);
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case 16:
|
case 16:
|
||||||
repeat = 3 + stream.readBits(2);
|
repeat = 3 + stream.readBits(2, "le");
|
||||||
while (repeat--) lengthTable[i++] = prev;
|
while (repeat--) lengthTable[i++] = prev;
|
||||||
break;
|
break;
|
||||||
case 17:
|
case 17:
|
||||||
repeat = 3 + stream.readBits(3);
|
repeat = 3 + stream.readBits(3, "le");
|
||||||
while (repeat--) lengthTable[i++] = 0;
|
while (repeat--) lengthTable[i++] = 0;
|
||||||
prev = 0;
|
prev = 0;
|
||||||
break;
|
break;
|
||||||
case 18:
|
case 18:
|
||||||
repeat = 11 + stream.readBits(7);
|
repeat = 11 + stream.readBits(7, "le");
|
||||||
while (repeat--) lengthTable[i++] = 0;
|
while (repeat--) lengthTable[i++] = 0;
|
||||||
prev = 0;
|
prev = 0;
|
||||||
break;
|
break;
|
||||||
@ -3886,11 +3931,11 @@ function parseHuffmanBlock(stream, litTab, distTab) {
|
|||||||
if (code < 256) continue;
|
if (code < 256) continue;
|
||||||
|
|
||||||
// Length code
|
// Length code
|
||||||
stream.readBits(lengthExtraTable[code - 257]);
|
stream.readBits(lengthExtraTable[code - 257], "le");
|
||||||
|
|
||||||
// Dist code
|
// Dist code
|
||||||
code = readHuffmanCode(stream, distTab);
|
code = readHuffmanCode(stream, distTab);
|
||||||
stream.readBits(distanceExtraTable[code]);
|
stream.readBits(distanceExtraTable[code], "le");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3948,7 +3993,7 @@ function readHuffmanCode(stream, table) {
|
|||||||
const [codeTable, maxCodeLength] = table;
|
const [codeTable, maxCodeLength] = table;
|
||||||
|
|
||||||
// Read max length
|
// Read max length
|
||||||
const bitsBuf = stream.readBits(maxCodeLength);
|
const bitsBuf = stream.readBits(maxCodeLength, "le");
|
||||||
const codeWithLength = codeTable[bitsBuf & ((1 << maxCodeLength) - 1)];
|
const codeWithLength = codeTable[bitsBuf & ((1 << maxCodeLength) - 1)];
|
||||||
const codeLength = codeWithLength >>> 16;
|
const codeLength = codeWithLength >>> 16;
|
||||||
|
|
||||||
|
@ -105,13 +105,17 @@ export function fromHex(data, delim="Auto", byteLen=2) {
|
|||||||
throw new OperationError("Byte length must be a positive integer");
|
throw new OperationError("Byte length must be a positive integer");
|
||||||
|
|
||||||
if (delim !== "None") {
|
if (delim !== "None") {
|
||||||
const delimRegex = delim === "Auto" ? /[^a-f\d]|(0x)/gi : Utils.regexRep(delim);
|
const delimRegex = delim === "Auto" ? /[^a-f\d]|0x/gi : Utils.regexRep(delim);
|
||||||
data = data.replace(delimRegex, "");
|
data = data.split(delimRegex);
|
||||||
|
} else {
|
||||||
|
data = [data];
|
||||||
}
|
}
|
||||||
|
|
||||||
const output = [];
|
const output = [];
|
||||||
for (let i = 0; i < data.length; i += byteLen) {
|
for (let i = 0; i < data.length; i++) {
|
||||||
output.push(parseInt(data.substr(i, byteLen), 16));
|
for (let j = 0; j < data[i].length; j += byteLen) {
|
||||||
|
output.push(parseInt(data[i].substr(j, byteLen), 16));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
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.version.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.version.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
|
||||||
|
}
|
||||||
|
}
|
244
src/core/lib/LS47.mjs
Normal file
244
src/core/lib/LS47.mjs
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
/**
|
||||||
|
* @author n1073645 [n1073645@gmail.com]
|
||||||
|
* @copyright Crown Copyright 2020
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
|
||||||
|
const letters = "_abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()";
|
||||||
|
const tiles = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the tiles with values and positions.
|
||||||
|
*/
|
||||||
|
export function initTiles() {
|
||||||
|
for (let i = 0; i < 49; i++)
|
||||||
|
tiles.push([letters.charAt(i), [Math.floor(i/7), i % 7]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotates the key "down".
|
||||||
|
*
|
||||||
|
* @param {string} key
|
||||||
|
* @param {number} col
|
||||||
|
* @param {number} n
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function rotateDown(key, col, n) {
|
||||||
|
const lines = [];
|
||||||
|
for (let i = 0; i < 7; i++)
|
||||||
|
lines.push(key.slice(i*7, (i + 1) * 7));
|
||||||
|
const lefts = [];
|
||||||
|
let mids = [];
|
||||||
|
const rights = [];
|
||||||
|
lines.forEach((element) => {
|
||||||
|
lefts.push(element.slice(0, col));
|
||||||
|
mids.push(element.charAt(col));
|
||||||
|
rights.push(element.slice(col+1));
|
||||||
|
});
|
||||||
|
n = (7 - n % 7) % 7;
|
||||||
|
mids = mids.slice(n).concat(mids.slice(0, n));
|
||||||
|
let result = "";
|
||||||
|
for (let i = 0; i < 7; i++)
|
||||||
|
result += lefts[i] + mids[i] + rights[i];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotates the key "right".
|
||||||
|
*
|
||||||
|
* @param {string} key
|
||||||
|
* @param {number} row
|
||||||
|
* @param {number} n
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function rotateRight(key, row, n) {
|
||||||
|
const mid = key.slice(row * 7, (row + 1) * 7);
|
||||||
|
n = (7 - n % 7) % 7;
|
||||||
|
return key.slice(0, 7 * row) + mid.slice(n) + mid.slice(0, n) + key.slice(7 * (row + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the position of a letter in the tiles.
|
||||||
|
*
|
||||||
|
* @param {string} letter
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function findIx(letter) {
|
||||||
|
for (let i = 0; i < tiles.length; i++)
|
||||||
|
if (tiles[i][0] === letter)
|
||||||
|
return tiles[i][1];
|
||||||
|
throw new OperationError("Letter " + letter + " is not included in LS47");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derives key from the input password.
|
||||||
|
*
|
||||||
|
* @param {string} password
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function deriveKey(password) {
|
||||||
|
let i = 0;
|
||||||
|
let k = letters;
|
||||||
|
for (const c of password) {
|
||||||
|
const [row, col] = findIx(c);
|
||||||
|
k = rotateDown(rotateRight(k, i, col), i, row);
|
||||||
|
i = (i + 1) % 7;
|
||||||
|
}
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the key is a valid key.
|
||||||
|
*
|
||||||
|
* @param {string} key
|
||||||
|
*/
|
||||||
|
function checkKey(key) {
|
||||||
|
if (key.length !== letters.length)
|
||||||
|
throw new OperationError("Wrong key size");
|
||||||
|
const counts = new Array();
|
||||||
|
for (let i = 0; i < letters.length; i++)
|
||||||
|
counts[letters.charAt(i)] = 0;
|
||||||
|
for (const elem of letters) {
|
||||||
|
if (letters.indexOf(elem) === -1)
|
||||||
|
throw new OperationError("Letter " + elem + " not in LS47");
|
||||||
|
counts[elem]++;
|
||||||
|
if (counts[elem] > 1)
|
||||||
|
throw new OperationError("Letter duplicated in the key");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the position of a letter in they key.
|
||||||
|
*
|
||||||
|
* @param {letter} key
|
||||||
|
* @param {string} letter
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
|
function findPos (key, letter) {
|
||||||
|
const index = key.indexOf(letter);
|
||||||
|
if (index >= 0 && index < 49)
|
||||||
|
return [Math.floor(index/7), index%7];
|
||||||
|
throw new OperationError("Letter " + letter + " is not in the key");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the character at the position on the tiles.
|
||||||
|
*
|
||||||
|
* @param {string} key
|
||||||
|
* @param {object} coord
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function findAtPos(key, coord) {
|
||||||
|
return key.charAt(coord[1] + (coord[0] * 7));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns new position by adding two positions.
|
||||||
|
*
|
||||||
|
* @param {object} a
|
||||||
|
* @param {object} b
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
|
function addPos(a, b) {
|
||||||
|
return [(a[0] + b[0]) % 7, (a[1] + b[1]) % 7];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns new position by subtracting two positions.
|
||||||
|
* Note: We have to manually do the remainder division, since JS does not
|
||||||
|
* operate correctly on negative numbers (e.g. -3 % 4 = -3 when it should be 1).
|
||||||
|
*
|
||||||
|
* @param {object} a
|
||||||
|
* @param {object} b
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
|
function subPos(a, b) {
|
||||||
|
const asub = a[0] - b[0];
|
||||||
|
const bsub = a[1] - b[1];
|
||||||
|
return [asub - (Math.floor(asub/7) * 7), bsub - (Math.floor(bsub/7) * 7)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts the plaintext string.
|
||||||
|
*
|
||||||
|
* @param {string} key
|
||||||
|
* @param {string} plaintext
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function encrypt(key, plaintext) {
|
||||||
|
checkKey(key);
|
||||||
|
let mp = [0, 0];
|
||||||
|
let ciphertext = "";
|
||||||
|
for (const p of plaintext) {
|
||||||
|
const pp = findPos(key, p);
|
||||||
|
const mix = findIx(findAtPos(key, mp));
|
||||||
|
let cp = addPos(pp, mix);
|
||||||
|
const c = findAtPos(key, cp);
|
||||||
|
ciphertext += c;
|
||||||
|
key = rotateRight(key, pp[0], 1);
|
||||||
|
cp = findPos(key, c);
|
||||||
|
key = rotateDown(key, cp[1], 1);
|
||||||
|
mp = addPos(mp, findIx(c));
|
||||||
|
}
|
||||||
|
return ciphertext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts the ciphertext string.
|
||||||
|
*
|
||||||
|
* @param {string} key
|
||||||
|
* @param {string} ciphertext
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function decrypt(key, ciphertext) {
|
||||||
|
checkKey(key);
|
||||||
|
let mp = [0, 0];
|
||||||
|
let plaintext = "";
|
||||||
|
for (const c of ciphertext) {
|
||||||
|
let cp = findPos(key, c);
|
||||||
|
const mix = findIx(findAtPos(key, mp));
|
||||||
|
const pp = subPos(cp, mix);
|
||||||
|
const p = findAtPos(key, pp);
|
||||||
|
plaintext += p;
|
||||||
|
key = rotateRight(key, pp[0], 1);
|
||||||
|
cp = findPos(key, c);
|
||||||
|
key = rotateDown(key, cp[1], 1);
|
||||||
|
mp = addPos(mp, findIx(c));
|
||||||
|
}
|
||||||
|
return plaintext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds padding to the input.
|
||||||
|
*
|
||||||
|
* @param {string} key
|
||||||
|
* @param {string} plaintext
|
||||||
|
* @param {string} signature
|
||||||
|
* @param {number} paddingSize
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function encryptPad(key, plaintext, signature, paddingSize) {
|
||||||
|
initTiles();
|
||||||
|
checkKey(key);
|
||||||
|
let padding = "";
|
||||||
|
for (let i = 0; i < paddingSize; i++) {
|
||||||
|
padding += letters.charAt(Math.floor(Math.random() * letters.length));
|
||||||
|
}
|
||||||
|
return encrypt(key, padding+plaintext+"---"+signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes padding from the ouput.
|
||||||
|
*
|
||||||
|
* @param {string} key
|
||||||
|
* @param {string} ciphertext
|
||||||
|
* @param {number} paddingSize
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function decryptPad(key, ciphertext, paddingSize) {
|
||||||
|
initTiles();
|
||||||
|
checkKey(key);
|
||||||
|
return decrypt(key, ciphertext).slice(paddingSize);
|
||||||
|
}
|
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;
|
||||||
|
}
|
21
src/core/lib/LZString.mjs
Normal file
21
src/core/lib/LZString.mjs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* lz-string exports.
|
||||||
|
*
|
||||||
|
* @author crespyl [peter@crespyl.net]
|
||||||
|
* @copyright Peter Jacobs 2021
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import LZString from "lz-string";
|
||||||
|
|
||||||
|
export const COMPRESSION_OUTPUT_FORMATS = ["default", "UTF16", "Base64"];
|
||||||
|
export const COMPRESSION_FUNCTIONS = {
|
||||||
|
"default": LZString.compress,
|
||||||
|
"UTF16": LZString.compressToUTF16,
|
||||||
|
"Base64": LZString.compressToBase64,
|
||||||
|
};
|
||||||
|
export const DECOMPRESSION_FUNCTIONS = {
|
||||||
|
"default": LZString.decompress,
|
||||||
|
"UTF16": LZString.decompressFromUTF16,
|
||||||
|
"Base64": LZString.decompressFromBase64,
|
||||||
|
};
|
@ -1,8 +1,9 @@
|
|||||||
import OperationConfig from "../config/OperationConfig.json";
|
import OperationConfig from "../config/OperationConfig.json" assert {type: "json"};
|
||||||
import Utils, { isWorkerEnvironment } from "../Utils.mjs";
|
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,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import Utils from "../Utils.mjs";
|
import Utils from "../Utils.mjs";
|
||||||
|
import protobuf from "protobufjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Protobuf lib. Contains functions to decode protobuf serialised
|
* Protobuf lib. Contains functions to decode protobuf serialised
|
||||||
@ -32,9 +33,10 @@ class Protobuf {
|
|||||||
this.MSB = 0x80;
|
this.MSB = 0x80;
|
||||||
this.VALUE = 0x7f;
|
this.VALUE = 0x7f;
|
||||||
|
|
||||||
// Declare offset and length
|
// Declare offset, length, and field type object
|
||||||
this.offset = 0;
|
this.offset = 0;
|
||||||
this.LENGTH = data.length;
|
this.LENGTH = data.length;
|
||||||
|
this.fieldTypes = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public Functions
|
// Public Functions
|
||||||
@ -76,15 +78,281 @@ class Protobuf {
|
|||||||
return pb._varInt();
|
return pb._varInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode input JSON according to the given schema
|
||||||
|
*
|
||||||
|
* @param {Object} input
|
||||||
|
* @param {Object []} args
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
static encode(input, args) {
|
||||||
|
this.updateProtoRoot(args[0]);
|
||||||
|
if (!this.mainMessageName) {
|
||||||
|
throw new Error("Schema Error: Schema not defined");
|
||||||
|
}
|
||||||
|
const message = this.parsedProto.root.nested[this.mainMessageName];
|
||||||
|
|
||||||
|
// Convert input into instance of message, and verify instance
|
||||||
|
input = message.fromObject(input);
|
||||||
|
const error = message.verify(input);
|
||||||
|
if (error) {
|
||||||
|
throw new Error("Input Error: " + error);
|
||||||
|
}
|
||||||
|
// Encode input
|
||||||
|
const output = message.encode(input).finish();
|
||||||
|
return new Uint8Array(output).buffer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse Protobuf data
|
* Parse Protobuf data
|
||||||
*
|
*
|
||||||
* @param {byteArray} input
|
* @param {byteArray} input
|
||||||
* @returns {Object}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
static decode(input) {
|
static decode(input, args) {
|
||||||
|
this.updateProtoRoot(args[0]);
|
||||||
|
this.showUnknownFields = args[1];
|
||||||
|
this.showTypes = args[2];
|
||||||
|
return this.mergeDecodes(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the parsedProto, throw parsing errors
|
||||||
|
*
|
||||||
|
* @param {string} protoText
|
||||||
|
*/
|
||||||
|
static updateProtoRoot(protoText) {
|
||||||
|
try {
|
||||||
|
this.parsedProto = protobuf.parse(protoText);
|
||||||
|
if (this.parsedProto.package) {
|
||||||
|
this.parsedProto.root = this.parsedProto.root.nested[this.parsedProto.package];
|
||||||
|
}
|
||||||
|
this.updateMainMessageName();
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error("Schema " + error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set mainMessageName to the first instance of a message defined in the schema that is not a submessage
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static updateMainMessageName() {
|
||||||
|
const messageNames = [];
|
||||||
|
const fieldTypes = [];
|
||||||
|
this.parsedProto.root.nestedArray.forEach(block => {
|
||||||
|
if (block instanceof protobuf.Type) {
|
||||||
|
messageNames.push(block.name);
|
||||||
|
this.parsedProto.root.nested[block.name].fieldsArray.forEach(field => {
|
||||||
|
fieldTypes.push(field.type);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (messageNames.length === 0) {
|
||||||
|
this.mainMessageName = null;
|
||||||
|
} else {
|
||||||
|
// for (const name of messageNames) {
|
||||||
|
// if (!fieldTypes.includes(name)) {
|
||||||
|
// this.mainMessageName = name;
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
this.mainMessageName = messageNames[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode input using Protobufjs package and raw methods, compare, and merge results
|
||||||
|
*
|
||||||
|
* @param {byteArray} input
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
static mergeDecodes(input) {
|
||||||
const pb = new Protobuf(input);
|
const pb = new Protobuf(input);
|
||||||
return pb._parse();
|
let rawDecode = pb._parse();
|
||||||
|
let message;
|
||||||
|
|
||||||
|
if (this.showTypes) {
|
||||||
|
rawDecode = this.showRawTypes(rawDecode, pb.fieldTypes);
|
||||||
|
this.parsedProto.root = this.appendTypesToFieldNames(this.parsedProto.root);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
message = this.parsedProto.root.nested[this.mainMessageName];
|
||||||
|
const packageDecode = message.toObject(message.decode(input), {
|
||||||
|
bytes: String,
|
||||||
|
longs: Number,
|
||||||
|
enums: String,
|
||||||
|
defaults: true
|
||||||
|
});
|
||||||
|
const output = {};
|
||||||
|
|
||||||
|
if (this.showUnknownFields) {
|
||||||
|
output[message.name] = packageDecode;
|
||||||
|
output["Unknown Fields"] = this.compareFields(rawDecode, message);
|
||||||
|
return output;
|
||||||
|
} else {
|
||||||
|
return packageDecode;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
if (message) {
|
||||||
|
throw new Error("Input " + error);
|
||||||
|
} else {
|
||||||
|
return rawDecode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace fieldnames with fieldname and type
|
||||||
|
*
|
||||||
|
* @param {Object} schemaRoot
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
static appendTypesToFieldNames(schemaRoot) {
|
||||||
|
for (const block of schemaRoot.nestedArray) {
|
||||||
|
if (block instanceof protobuf.Type) {
|
||||||
|
for (const [fieldName, fieldData] of Object.entries(block.fields)) {
|
||||||
|
schemaRoot.nested[block.name].remove(block.fields[fieldName]);
|
||||||
|
schemaRoot.nested[block.name].add(new protobuf.Field(`${fieldName} (${fieldData.type})`, fieldData.id, fieldData.type, fieldData.rule));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return schemaRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add field type to field name for fields in the raw decoded output
|
||||||
|
*
|
||||||
|
* @param {Object} rawDecode
|
||||||
|
* @param {Object} fieldTypes
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
static showRawTypes(rawDecode, fieldTypes) {
|
||||||
|
for (const [fieldNum, value] of Object.entries(rawDecode)) {
|
||||||
|
const fieldType = fieldTypes[fieldNum];
|
||||||
|
let outputFieldValue;
|
||||||
|
let outputFieldType;
|
||||||
|
|
||||||
|
// Submessages
|
||||||
|
if (isNaN(fieldType)) {
|
||||||
|
outputFieldType = 2;
|
||||||
|
|
||||||
|
// Repeated submessages
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
const fieldInstances = [];
|
||||||
|
for (const instance of Object.keys(value)) {
|
||||||
|
if (typeof(value[instance]) !== "string") {
|
||||||
|
fieldInstances.push(this.showRawTypes(value[instance], fieldType));
|
||||||
|
} else {
|
||||||
|
fieldInstances.push(value[instance]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outputFieldValue = fieldInstances;
|
||||||
|
|
||||||
|
// Single submessage
|
||||||
|
} else {
|
||||||
|
outputFieldValue = this.showRawTypes(value, fieldType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-submessage field
|
||||||
|
} else {
|
||||||
|
outputFieldType = fieldType;
|
||||||
|
outputFieldValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Substitute fieldNum with field number and type
|
||||||
|
rawDecode[`field #${fieldNum}: ${this.getTypeInfo(outputFieldType)}`] = outputFieldValue;
|
||||||
|
delete rawDecode[fieldNum];
|
||||||
|
}
|
||||||
|
return rawDecode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare raw decode to package decode and return discrepancies
|
||||||
|
*
|
||||||
|
* @param rawDecodedMessage
|
||||||
|
* @param schemaMessage
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
static compareFields(rawDecodedMessage, schemaMessage) {
|
||||||
|
// Define message data using raw decode output and schema
|
||||||
|
const schemaFieldProperties = {};
|
||||||
|
const schemaFieldNames = Object.keys(schemaMessage.fields);
|
||||||
|
schemaFieldNames.forEach(field => schemaFieldProperties[schemaMessage.fields[field].id] = field);
|
||||||
|
|
||||||
|
// Loop over each field present in the raw decode output
|
||||||
|
for (const fieldName in rawDecodedMessage) {
|
||||||
|
let fieldId;
|
||||||
|
if (isNaN(fieldName)) {
|
||||||
|
fieldId = fieldName.match(/^field #(\d+)/)[1];
|
||||||
|
} else {
|
||||||
|
fieldId = fieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this field is defined in the schema
|
||||||
|
if (fieldId in schemaFieldProperties) {
|
||||||
|
const schemaFieldName = schemaFieldProperties[fieldId];
|
||||||
|
|
||||||
|
// Extract the current field data from the raw decode and schema
|
||||||
|
const rawFieldData = rawDecodedMessage[fieldName];
|
||||||
|
const schemaField = schemaMessage.fields[schemaFieldName];
|
||||||
|
|
||||||
|
// Check for repeated fields
|
||||||
|
if (Array.isArray(rawFieldData) && !schemaField.repeated) {
|
||||||
|
rawDecodedMessage[`(${schemaMessage.name}) ${schemaFieldName} is a repeated field`] = rawFieldData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for submessage fields
|
||||||
|
if (schemaField.resolvedType instanceof protobuf.Type) {
|
||||||
|
const subMessageType = schemaMessage.fields[schemaFieldName].type;
|
||||||
|
const schemaSubMessage = this.parsedProto.root.nested[subMessageType];
|
||||||
|
const rawSubMessages = rawDecodedMessage[fieldName];
|
||||||
|
let rawDecodedSubMessage = {};
|
||||||
|
|
||||||
|
// Squash multiple submessage instances into one submessage
|
||||||
|
if (Array.isArray(rawSubMessages)) {
|
||||||
|
rawSubMessages.forEach(subMessageInstance => {
|
||||||
|
const instanceFields = Object.entries(subMessageInstance);
|
||||||
|
instanceFields.forEach(subField => {
|
||||||
|
rawDecodedSubMessage[subField[0]] = subField[1];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
rawDecodedSubMessage = rawSubMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Treat submessage as own message and compare its fields
|
||||||
|
rawDecodedSubMessage = Protobuf.compareFields(rawDecodedSubMessage, schemaSubMessage);
|
||||||
|
if (Object.entries(rawDecodedSubMessage).length !== 0) {
|
||||||
|
rawDecodedMessage[`${schemaFieldName} (${subMessageType}) has missing fields`] = rawDecodedSubMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete rawDecodedMessage[fieldName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rawDecodedMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns wiretype information for input wiretype number
|
||||||
|
*
|
||||||
|
* @param {number} wireType
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
static getTypeInfo(wireType) {
|
||||||
|
switch (wireType) {
|
||||||
|
case 0:
|
||||||
|
return "VarInt (e.g. int32, bool)";
|
||||||
|
case 1:
|
||||||
|
return "64-Bit (e.g. fixed64, double)";
|
||||||
|
case 2:
|
||||||
|
return "L-delim (e.g. string, message)";
|
||||||
|
case 5:
|
||||||
|
return "32-Bit (e.g. fixed32, float)";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private Class Functions
|
// Private Class Functions
|
||||||
@ -143,6 +411,11 @@ class Protobuf {
|
|||||||
const header = this._fieldHeader();
|
const header = this._fieldHeader();
|
||||||
const type = header.type;
|
const type = header.type;
|
||||||
const key = header.key;
|
const key = header.key;
|
||||||
|
|
||||||
|
if (typeof(this.fieldTypes[key]) !== "object") {
|
||||||
|
this.fieldTypes[key] = type;
|
||||||
|
}
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
// varint
|
// varint
|
||||||
case 0:
|
case 0:
|
||||||
@ -152,7 +425,7 @@ class Protobuf {
|
|||||||
return { "key": key, "value": this._uint64() };
|
return { "key": key, "value": this._uint64() };
|
||||||
// length delimited
|
// length delimited
|
||||||
case 2:
|
case 2:
|
||||||
return { "key": key, "value": this._lenDelim() };
|
return { "key": key, "value": this._lenDelim(key) };
|
||||||
// fixed 32
|
// fixed 32
|
||||||
case 5:
|
case 5:
|
||||||
return { "key": key, "value": this._uint32() };
|
return { "key": key, "value": this._uint32() };
|
||||||
@ -237,10 +510,10 @@ class Protobuf {
|
|||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
_uint64() {
|
_uint64() {
|
||||||
// Read off a Uint64
|
// Read off a Uint64 with little-endian
|
||||||
let num = this.data[this.offset++] * 0x1000000 + (this.data[this.offset++] << 16) + (this.data[this.offset++] << 8) + this.data[this.offset++];
|
const lowerHalf = this.data[this.offset++] + (this.data[this.offset++] * 0x100) + (this.data[this.offset++] * 0x10000) + this.data[this.offset++] * 0x1000000;
|
||||||
num = num * 0x100000000 + this.data[this.offset++] * 0x1000000 + (this.data[this.offset++] << 16) + (this.data[this.offset++] << 8) + this.data[this.offset++];
|
const upperHalf = this.data[this.offset++] + (this.data[this.offset++] * 0x100) + (this.data[this.offset++] * 0x10000) + this.data[this.offset++] * 0x1000000;
|
||||||
return num;
|
return upperHalf * 0x100000000 + lowerHalf;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -249,7 +522,7 @@ class Protobuf {
|
|||||||
* @private
|
* @private
|
||||||
* @returns {Object|string}
|
* @returns {Object|string}
|
||||||
*/
|
*/
|
||||||
_lenDelim() {
|
_lenDelim(fieldNum) {
|
||||||
// Read off the field length
|
// Read off the field length
|
||||||
const length = this._varInt();
|
const length = this._varInt();
|
||||||
const fieldBytes = this.data.slice(this.offset, this.offset + length);
|
const fieldBytes = this.data.slice(this.offset, this.offset + length);
|
||||||
@ -258,6 +531,10 @@ class Protobuf {
|
|||||||
// Attempt to parse as a new Protobuf Object
|
// Attempt to parse as a new Protobuf Object
|
||||||
const pbObject = new Protobuf(fieldBytes);
|
const pbObject = new Protobuf(fieldBytes);
|
||||||
field = pbObject._parse();
|
field = pbObject._parse();
|
||||||
|
|
||||||
|
// Set field types object
|
||||||
|
this.fieldTypes[fieldNum] = {...this.fieldTypes[fieldNum], ...pbObject.fieldTypes};
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Otherwise treat as bytes
|
// Otherwise treat as bytes
|
||||||
field = Utils.byteArrayToChars(fieldBytes);
|
field = Utils.byteArrayToChars(fieldBytes);
|
||||||
@ -276,7 +553,7 @@ class Protobuf {
|
|||||||
_uint32() {
|
_uint32() {
|
||||||
// Use a dataview to read off the integer
|
// Use a dataview to read off the integer
|
||||||
const dataview = new DataView(new Uint8Array(this.data.slice(this.offset, this.offset + 4)).buffer);
|
const dataview = new DataView(new Uint8Array(this.data.slice(this.offset, this.offset + 4)).buffer);
|
||||||
const value = dataview.getUint32(0);
|
const value = dataview.getUint32(0, true);
|
||||||
this.offset += 4;
|
this.offset += 4;
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
47
src/core/lib/Protocol.mjs
Normal file
47
src/core/lib/Protocol.mjs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* Protocol parsing functions.
|
||||||
|
*
|
||||||
|
* @author n1474335 [n1474335@gmail.com]
|
||||||
|
* @copyright Crown Copyright 2022
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import BigNumber from "bignumber.js";
|
||||||
|
import {toHexFast} from "../lib/Hex.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively displays a JSON object as an HTML table
|
||||||
|
*
|
||||||
|
* @param {Object} obj
|
||||||
|
* @returns string
|
||||||
|
*/
|
||||||
|
export function objToTable(obj, nested=false) {
|
||||||
|
let html = `<table
|
||||||
|
class='table table-sm table-nonfluid ${nested ? "mb-0 table-borderless" : "table-bordered"}'
|
||||||
|
style='table-layout: fixed; ${nested ? "margin: -1px !important;" : ""}'>`;
|
||||||
|
if (!nested)
|
||||||
|
html += `<tr>
|
||||||
|
<th>Field</th>
|
||||||
|
<th>Value</th>
|
||||||
|
</tr>`;
|
||||||
|
|
||||||
|
for (const key in obj) {
|
||||||
|
html += `<tr><td style='word-wrap: break-word'>${key}</td>`;
|
||||||
|
if (typeof obj[key] === "object")
|
||||||
|
html += `<td style='padding: 0'>${objToTable(obj[key], true)}</td>`;
|
||||||
|
else
|
||||||
|
html += `<td>${obj[key]}</td>`;
|
||||||
|
html += "</tr>";
|
||||||
|
}
|
||||||
|
html += "</table>";
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts bytes into a BigNumber string
|
||||||
|
* @param {Uint8Array} bs
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function bytesToLargeNumber(bs) {
|
||||||
|
return BigNumber(toHexFast(bs), 16).toString();
|
||||||
|
}
|
@ -9,35 +9,25 @@
|
|||||||
import { toHex, fromHex } from "./Hex.mjs";
|
import { toHex, fromHex } from "./Hex.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats Distinguished Name (DN) strings.
|
* Formats Distinguished Name (DN) objects to strings.
|
||||||
*
|
*
|
||||||
* @param {string} dnStr
|
* @param {Object} dnObj
|
||||||
* @param {number} indent
|
* @param {number} indent
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function formatDnStr(dnStr, indent) {
|
export function formatDnObj(dnObj, indent) {
|
||||||
const fields = dnStr.substr(1).replace(/([^\\])\//g, "$1$1/").split(/[^\\]\//);
|
let output = "";
|
||||||
let output = "",
|
|
||||||
maxKeyLen = 0,
|
|
||||||
key,
|
|
||||||
value,
|
|
||||||
i,
|
|
||||||
str;
|
|
||||||
|
|
||||||
for (i = 0; i < fields.length; i++) {
|
const maxKeyLen = dnObj.array.reduce((max, item) => {
|
||||||
if (!fields[i].length) continue;
|
return item[0].type.length > max ? item[0].type.length : max;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
key = fields[i].split("=")[0];
|
for (let i = 0; i < dnObj.array.length; i++) {
|
||||||
|
if (!dnObj.array[i].length) continue;
|
||||||
|
|
||||||
maxKeyLen = key.length > maxKeyLen ? key.length : maxKeyLen;
|
const key = dnObj.array[i][0].type;
|
||||||
}
|
const value = dnObj.array[i][0].value;
|
||||||
|
const str = `${key.padEnd(maxKeyLen, " ")} = ${value}\n`;
|
||||||
for (i = 0; i < fields.length; i++) {
|
|
||||||
if (!fields[i].length) continue;
|
|
||||||
|
|
||||||
key = fields[i].split("=")[0];
|
|
||||||
value = fields[i].split("=")[1];
|
|
||||||
str = key.padEnd(maxKeyLen, " ") + " = " + value + "\n";
|
|
||||||
|
|
||||||
output += str.padStart(indent + str.length, " ");
|
output += str.padStart(indent + str.length, " ");
|
||||||
}
|
}
|
||||||
|
@ -10,8 +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 jimplib from "jimp/es/index.js";
|
import Jimp from "jimp/es/index.js";
|
||||||
const jimp = jimplib.default ? jimplib.default : jimplib;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a QR code image from an image
|
* Parses a QR code image from an image
|
||||||
@ -23,7 +22,7 @@ const jimp = jimplib.default ? jimplib.default : jimplib;
|
|||||||
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})`);
|
||||||
}
|
}
|
||||||
@ -34,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})`);
|
||||||
|
502
src/core/lib/SIGABA.mjs
Normal file
502
src/core/lib/SIGABA.mjs
Normal file
@ -0,0 +1,502 @@
|
|||||||
|
/**
|
||||||
|
* Emulation of the SIGABA machine
|
||||||
|
*
|
||||||
|
* @author hettysymes
|
||||||
|
* @copyright hettysymes 2020
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set of randomised example SIGABA cipher/control rotors (these rotors are interchangeable). Cipher and control rotors can be referred to as C and R rotors respectively.
|
||||||
|
*/
|
||||||
|
export const CR_ROTORS = [
|
||||||
|
{name: "Example 1", value: "SRGWANHPJZFXVIDQCEUKBYOLMT"},
|
||||||
|
{name: "Example 2", value: "THQEFSAZVKJYULBODCPXNIMWRG"},
|
||||||
|
{name: "Example 3", value: "XDTUYLEVFNQZBPOGIRCSMHWKAJ"},
|
||||||
|
{name: "Example 4", value: "LOHDMCWUPSTNGVXYFJREQIKBZA"},
|
||||||
|
{name: "Example 5", value: "ERXWNZQIJYLVOFUMSGHTCKPBDA"},
|
||||||
|
{name: "Example 6", value: "FQECYHJIOUMDZVPSLKRTGWXBAN"},
|
||||||
|
{name: "Example 7", value: "TBYIUMKZDJSOPEWXVANHLCFQGR"},
|
||||||
|
{name: "Example 8", value: "QZUPDTFNYIAOMLEBWJXCGHKRSV"},
|
||||||
|
{name: "Example 9", value: "CZWNHEMPOVXLKRSIDGJFYBTQAU"},
|
||||||
|
{name: "Example 10", value: "ENPXJVKYQBFZTICAGMOHWRLDUS"}
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set of randomised example SIGABA index rotors (may be referred to as I rotors).
|
||||||
|
*/
|
||||||
|
export const I_ROTORS = [
|
||||||
|
{name: "Example 1", value: "6201348957"},
|
||||||
|
{name: "Example 2", value: "6147253089"},
|
||||||
|
{name: "Example 3", value: "8239647510"},
|
||||||
|
{name: "Example 4", value: "7194835260"},
|
||||||
|
{name: "Example 5", value: "4873205916"}
|
||||||
|
];
|
||||||
|
|
||||||
|
export const NUMBERS = "0123456789".split("");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a letter to uppercase (if it already isn't)
|
||||||
|
*
|
||||||
|
* @param {char} letter - letter to convert to uppercase
|
||||||
|
* @returns {char}
|
||||||
|
*/
|
||||||
|
export function convToUpperCase(letter) {
|
||||||
|
const charCode = letter.charCodeAt();
|
||||||
|
if (97<=charCode && charCode<=122) {
|
||||||
|
return String.fromCharCode(charCode-32);
|
||||||
|
}
|
||||||
|
return letter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The SIGABA machine consisting of the 3 rotor banks: cipher, control and index banks.
|
||||||
|
*/
|
||||||
|
export class SigabaMachine {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SigabaMachine constructor
|
||||||
|
*
|
||||||
|
* @param {Object[]} cipherRotors - list of CRRotors
|
||||||
|
* @param {Object[]} controlRotors - list of CRRotors
|
||||||
|
* @param {object[]} indexRotors - list of IRotors
|
||||||
|
*/
|
||||||
|
constructor(cipherRotors, controlRotors, indexRotors) {
|
||||||
|
this.cipherBank = new CipherBank(cipherRotors);
|
||||||
|
this.controlBank = new ControlBank(controlRotors);
|
||||||
|
this.indexBank = new IndexBank(indexRotors);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Steps all the correct rotors in the machine.
|
||||||
|
*/
|
||||||
|
step() {
|
||||||
|
const controlOut = this.controlBank.goThroughControl();
|
||||||
|
const indexOut = this.indexBank.goThroughIndex(controlOut);
|
||||||
|
this.cipherBank.step(indexOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts a letter. A space is converted to a "Z" before encryption, and a "Z" is converted to an "X". This allows spaces to be encrypted.
|
||||||
|
*
|
||||||
|
* @param {char} letter - letter to encrypt
|
||||||
|
* @returns {char}
|
||||||
|
*/
|
||||||
|
encryptLetter(letter) {
|
||||||
|
letter = convToUpperCase(letter);
|
||||||
|
if (letter === " ") {
|
||||||
|
letter = "Z";
|
||||||
|
} else if (letter === "Z") {
|
||||||
|
letter = "X";
|
||||||
|
}
|
||||||
|
const encryptedLetter = this.cipherBank.encrypt(letter);
|
||||||
|
this.step();
|
||||||
|
return encryptedLetter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts a letter. A letter decrypted as a "Z" is converted to a space before it is output, since spaces are converted to "Z"s before encryption.
|
||||||
|
*
|
||||||
|
* @param {char} letter - letter to decrypt
|
||||||
|
* @returns {char}
|
||||||
|
*/
|
||||||
|
decryptLetter(letter) {
|
||||||
|
letter = convToUpperCase(letter);
|
||||||
|
let decryptedLetter = this.cipherBank.decrypt(letter);
|
||||||
|
if (decryptedLetter === "Z") {
|
||||||
|
decryptedLetter = " ";
|
||||||
|
}
|
||||||
|
this.step();
|
||||||
|
return decryptedLetter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts a message of one or more letters
|
||||||
|
*
|
||||||
|
* @param {string} msg - message to encrypt
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
encrypt(msg) {
|
||||||
|
let ciphertext = "";
|
||||||
|
for (const letter of msg) {
|
||||||
|
ciphertext = ciphertext.concat(this.encryptLetter(letter));
|
||||||
|
}
|
||||||
|
return ciphertext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts a message of one or more letters
|
||||||
|
*
|
||||||
|
* @param {string} msg - message to decrypt
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
decrypt(msg) {
|
||||||
|
let plaintext = "";
|
||||||
|
for (const letter of msg) {
|
||||||
|
plaintext = plaintext.concat(this.decryptLetter(letter));
|
||||||
|
}
|
||||||
|
return plaintext;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cipher rotor bank consists of 5 cipher rotors in either a forward or reversed orientation.
|
||||||
|
*/
|
||||||
|
export class CipherBank {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CipherBank constructor
|
||||||
|
*
|
||||||
|
* @param {Object[]} rotors - list of CRRotors
|
||||||
|
*/
|
||||||
|
constructor(rotors) {
|
||||||
|
this.rotors = rotors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts a letter through the cipher rotors (signal goes from left-to-right)
|
||||||
|
*
|
||||||
|
* @param {char} inputPos - the input position of the signal (letter to be encrypted)
|
||||||
|
* @returns {char}
|
||||||
|
*/
|
||||||
|
encrypt(inputPos) {
|
||||||
|
for (const rotor of this.rotors) {
|
||||||
|
inputPos = rotor.crypt(inputPos, "leftToRight");
|
||||||
|
}
|
||||||
|
return inputPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts a letter through the cipher rotors (signal goes from right-to-left)
|
||||||
|
*
|
||||||
|
* @param {char} inputPos - the input position of the signal (letter to be decrypted)
|
||||||
|
* @returns {char}
|
||||||
|
*/
|
||||||
|
decrypt(inputPos) {
|
||||||
|
const revOrderedRotors = [...this.rotors].reverse();
|
||||||
|
for (const rotor of revOrderedRotors) {
|
||||||
|
inputPos = rotor.crypt(inputPos, "rightToLeft");
|
||||||
|
}
|
||||||
|
return inputPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step the cipher rotors forward according to the inputs from the index rotors
|
||||||
|
*
|
||||||
|
* @param {number[]} indexInputs - the inputs from the index rotors
|
||||||
|
*/
|
||||||
|
step(indexInputs) {
|
||||||
|
const logicDict = {0: [0, 9], 1: [7, 8], 2: [5, 6], 3: [3, 4], 4: [1, 2]};
|
||||||
|
const rotorsToMove = [];
|
||||||
|
for (const key in logicDict) {
|
||||||
|
const item = logicDict[key];
|
||||||
|
for (const i of indexInputs) {
|
||||||
|
if (item.includes(i)) {
|
||||||
|
rotorsToMove.push(this.rotors[key]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const rotor of rotorsToMove) {
|
||||||
|
rotor.step();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The control rotor bank consists of 5 control rotors in either a forward or reversed orientation. Signals to the control rotor bank always go from right-to-left.
|
||||||
|
*/
|
||||||
|
export class ControlBank {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ControlBank constructor. The rotors have been reversed as signals go from right-to-left through the control rotors.
|
||||||
|
*
|
||||||
|
* @param {Object[]} rotors - list of CRRotors
|
||||||
|
*/
|
||||||
|
constructor(rotors) {
|
||||||
|
this.rotors = [...rotors].reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts a letter.
|
||||||
|
*
|
||||||
|
* @param {char} inputPos - the input position of the signal
|
||||||
|
* @returns {char}
|
||||||
|
*/
|
||||||
|
crypt(inputPos) {
|
||||||
|
for (const rotor of this.rotors) {
|
||||||
|
inputPos = rotor.crypt(inputPos, "rightToLeft");
|
||||||
|
}
|
||||||
|
return inputPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the outputs of the control rotors. The inputs to the control rotors are always "F", "G", "H" and "I".
|
||||||
|
*
|
||||||
|
* @returns {number[]}
|
||||||
|
*/
|
||||||
|
getOutputs() {
|
||||||
|
const outputs = [this.crypt("F"), this.crypt("G"), this.crypt("H"), this.crypt("I")];
|
||||||
|
const logicDict = {1: "B", 2: "C", 3: "DE", 4: "FGH", 5: "IJK", 6: "LMNO", 7: "PQRST", 8: "UVWXYZ", 9: "A"};
|
||||||
|
const numberOutputs = [];
|
||||||
|
for (const key in logicDict) {
|
||||||
|
const item = logicDict[key];
|
||||||
|
for (const output of outputs) {
|
||||||
|
if (item.includes(output)) {
|
||||||
|
numberOutputs.push(key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return numberOutputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Steps the control rotors. Only 3 of the control rotors step: one after every encryption, one after every 26, and one after every 26 squared.
|
||||||
|
*/
|
||||||
|
step() {
|
||||||
|
const MRotor = this.rotors[1], FRotor = this.rotors[2], SRotor = this.rotors[3];
|
||||||
|
// 14 is the offset of "O" from "A" - the next rotor steps once the previous rotor reaches "O"
|
||||||
|
if (FRotor.state === 14) {
|
||||||
|
if (MRotor.state === 14) {
|
||||||
|
SRotor.step();
|
||||||
|
}
|
||||||
|
MRotor.step();
|
||||||
|
}
|
||||||
|
FRotor.step();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The goThroughControl function combines getting the outputs from the control rotor bank and then stepping them.
|
||||||
|
*
|
||||||
|
* @returns {number[]}
|
||||||
|
*/
|
||||||
|
goThroughControl() {
|
||||||
|
const outputs = this.getOutputs();
|
||||||
|
this.step();
|
||||||
|
return outputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The index rotor bank consists of 5 index rotors all placed in the forwards orientation.
|
||||||
|
*/
|
||||||
|
export class IndexBank {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IndexBank constructor
|
||||||
|
*
|
||||||
|
* @param {Object[]} rotors - list of IRotors
|
||||||
|
*/
|
||||||
|
constructor(rotors) {
|
||||||
|
this.rotors = rotors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts a number.
|
||||||
|
*
|
||||||
|
* @param {number} inputPos - the input position of the signal
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
crypt(inputPos) {
|
||||||
|
for (const rotor of this.rotors) {
|
||||||
|
inputPos = rotor.crypt(inputPos);
|
||||||
|
}
|
||||||
|
return inputPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The goThroughIndex function takes the inputs from the control rotor bank and returns the list of outputs after encryption through the index rotors.
|
||||||
|
*
|
||||||
|
* @param {number[]} controlInputs - inputs from the control rotors
|
||||||
|
* @returns {number[]}
|
||||||
|
*/
|
||||||
|
goThroughIndex(controlInputs) {
|
||||||
|
const outputs = [];
|
||||||
|
for (const inp of controlInputs) {
|
||||||
|
outputs.push(this.crypt(inp));
|
||||||
|
}
|
||||||
|
return outputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotor class
|
||||||
|
*/
|
||||||
|
export class Rotor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotor constructor
|
||||||
|
*
|
||||||
|
* @param {number[]} wireSetting - the wirings within the rotor: mapping from left-to-right, the index of the number in the list maps onto the number at that index
|
||||||
|
* @param {bool} rev - true if the rotor is reversed, false if it isn't
|
||||||
|
* @param {number} key - the starting position or state of the rotor
|
||||||
|
*/
|
||||||
|
constructor(wireSetting, key, rev) {
|
||||||
|
this.state = key;
|
||||||
|
this.numMapping = this.getNumMapping(wireSetting, rev);
|
||||||
|
this.posMapping = this.getPosMapping(rev);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number mapping from the wireSetting (only different from wireSetting if rotor is reversed)
|
||||||
|
*
|
||||||
|
* @param {number[]} wireSetting - the wirings within the rotors
|
||||||
|
* @param {bool} rev - true if reversed, false if not
|
||||||
|
* @returns {number[]}
|
||||||
|
*/
|
||||||
|
getNumMapping(wireSetting, rev) {
|
||||||
|
if (rev===false) {
|
||||||
|
return wireSetting;
|
||||||
|
} else {
|
||||||
|
const length = wireSetting.length;
|
||||||
|
const tempMapping = new Array(length);
|
||||||
|
for (let i=0; i<length; i++) {
|
||||||
|
tempMapping[wireSetting[i]] = i;
|
||||||
|
}
|
||||||
|
return tempMapping;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the position mapping (how the position numbers map onto the numbers of the rotor)
|
||||||
|
*
|
||||||
|
* @param {bool} rev - true if reversed, false if not
|
||||||
|
* @returns {number[]}
|
||||||
|
*/
|
||||||
|
getPosMapping(rev) {
|
||||||
|
const length = this.numMapping.length;
|
||||||
|
const posMapping = [];
|
||||||
|
if (rev===false) {
|
||||||
|
for (let i = this.state; i < this.state+length; i++) {
|
||||||
|
let res = i%length;
|
||||||
|
if (res<0) {
|
||||||
|
res += length;
|
||||||
|
}
|
||||||
|
posMapping.push(res);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = this.state; i > this.state-length; i--) {
|
||||||
|
let res = i%length;
|
||||||
|
if (res<0) {
|
||||||
|
res += length;
|
||||||
|
}
|
||||||
|
posMapping.push(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return posMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt/decrypt data. This process is identical to the rotors of cipher machines such as Enigma or Typex.
|
||||||
|
*
|
||||||
|
* @param {number} inputPos - the input position of the signal (the data to encrypt/decrypt)
|
||||||
|
* @param {string} direction - one of "leftToRight" and "rightToLeft", states the direction in which the signal passes through the rotor
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
cryptNum(inputPos, direction) {
|
||||||
|
const inpNum = this.posMapping[inputPos];
|
||||||
|
let outNum;
|
||||||
|
if (direction === "leftToRight") {
|
||||||
|
outNum = this.numMapping[inpNum];
|
||||||
|
} else if (direction === "rightToLeft") {
|
||||||
|
outNum = this.numMapping.indexOf(inpNum);
|
||||||
|
}
|
||||||
|
const outPos = this.posMapping.indexOf(outNum);
|
||||||
|
return outPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Steps the rotor. The number at position 0 will be moved to position 1 etc.
|
||||||
|
*/
|
||||||
|
step() {
|
||||||
|
const lastNum = this.posMapping.pop();
|
||||||
|
this.posMapping.splice(0, 0, lastNum);
|
||||||
|
this.state = this.posMapping[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A CRRotor is a cipher (C) or control (R) rotor. These rotors are identical and interchangeable. A C or R rotor consists of 26 contacts, one for each letter, and may be put into either a forwards of reversed orientation.
|
||||||
|
*/
|
||||||
|
export class CRRotor extends Rotor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CRRotor constructor
|
||||||
|
*
|
||||||
|
* @param {string} wireSetting - the rotor wirings (string of letters)
|
||||||
|
* @param {char} key - initial state of rotor
|
||||||
|
* @param {bool} rev - true if reversed, false if not
|
||||||
|
*/
|
||||||
|
constructor(wireSetting, key, rev=false) {
|
||||||
|
wireSetting = wireSetting.split("").map(CRRotor.letterToNum);
|
||||||
|
super(wireSetting, CRRotor.letterToNum(key), rev);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static function which converts a letter into its number i.e. its offset from the letter "A"
|
||||||
|
*
|
||||||
|
* @param {char} letter - letter to convert to number
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
static letterToNum(letter) {
|
||||||
|
return letter.charCodeAt()-65;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static function which converts a number (a letter's offset from "A") into its letter
|
||||||
|
*
|
||||||
|
* @param {number} num - number to convert to letter
|
||||||
|
* @returns {char}
|
||||||
|
*/
|
||||||
|
static numToLetter(num) {
|
||||||
|
return String.fromCharCode(num+65);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts/decrypts a letter.
|
||||||
|
*
|
||||||
|
* @param {char} inputPos - the input position of the signal ("A" refers to position 0 etc.)
|
||||||
|
* @param {string} direction - one of "leftToRight" and "rightToLeft"
|
||||||
|
* @returns {char}
|
||||||
|
*/
|
||||||
|
crypt(inputPos, direction) {
|
||||||
|
inputPos = CRRotor.letterToNum(inputPos);
|
||||||
|
const outPos = this.cryptNum(inputPos, direction);
|
||||||
|
return CRRotor.numToLetter(outPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An IRotor is an index rotor, which consists of 10 contacts each numbered from 0 to 9. Unlike C and R rotors, they cannot be put in the reversed orientation. The index rotors do not step at any point during encryption or decryption.
|
||||||
|
*/
|
||||||
|
export class IRotor extends Rotor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IRotor constructor
|
||||||
|
*
|
||||||
|
* @param {string} wireSetting - the rotor wirings (string of numbers)
|
||||||
|
* @param {char} key - initial state of rotor
|
||||||
|
*/
|
||||||
|
constructor(wireSetting, key) {
|
||||||
|
wireSetting = wireSetting.split("").map(Number);
|
||||||
|
super(wireSetting, Number(key), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts a number
|
||||||
|
*
|
||||||
|
* @param {number} inputPos - the input position of the signal
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
crypt(inputPos) {
|
||||||
|
return this.cryptNum(inputPos, "leftToRight");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
331
src/core/lib/SM4.mjs
Normal file
331
src/core/lib/SM4.mjs
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
/**
|
||||||
|
* Complete implementation of SM4 cipher encryption/decryption with
|
||||||
|
* ECB, CBC, CFB, OFB, CTR block modes.
|
||||||
|
* These modes are specified in IETF draft-ribose-cfrg-sm4-09, see:
|
||||||
|
* https://tools.ietf.org/id/draft-ribose-cfrg-sm4-09.html
|
||||||
|
* for details.
|
||||||
|
*
|
||||||
|
* Follows spec from Cryptography Standardization Technical Comittee:
|
||||||
|
* http://www.gmbz.org.cn/upload/2018-04-04/1522788048733065051.pdf
|
||||||
|
*
|
||||||
|
* @author swesven
|
||||||
|
* @copyright 2021
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
|
||||||
|
/** Number of rounds */
|
||||||
|
const NROUNDS = 32;
|
||||||
|
|
||||||
|
/** block size in bytes */
|
||||||
|
const BLOCKSIZE = 16;
|
||||||
|
|
||||||
|
/** The S box, 256 8-bit values */
|
||||||
|
const Sbox = [
|
||||||
|
0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c, 0x05,
|
||||||
|
0x2b, 0x67, 0x9a, 0x76, 0x2a, 0xbe, 0x04, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99,
|
||||||
|
0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef, 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, 0xed, 0xcf, 0xac, 0x62,
|
||||||
|
0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x08, 0xe8, 0x95, 0x80, 0xdf, 0x94, 0xfa, 0x75, 0x8f, 0x3f, 0xa6,
|
||||||
|
0x47, 0x07, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c, 0x19, 0xe6, 0x85, 0x4f, 0xa8,
|
||||||
|
0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb, 0x0f, 0x4b, 0x70, 0x56, 0x9d, 0x35,
|
||||||
|
0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, 0x87,
|
||||||
|
0xd4, 0x00, 0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52, 0x4c, 0x36, 0x02, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e,
|
||||||
|
0xea, 0xbf, 0x8a, 0xd2, 0x40, 0xc7, 0x38, 0xb5, 0xa3, 0xf7, 0xf2, 0xce, 0xf9, 0x61, 0x15, 0xa1,
|
||||||
|
0xe0, 0xae, 0x5d, 0xa4, 0x9b, 0x34, 0x1a, 0x55, 0xad, 0x93, 0x32, 0x30, 0xf5, 0x8c, 0xb1, 0xe3,
|
||||||
|
0x1d, 0xf6, 0xe2, 0x2e, 0x82, 0x66, 0xca, 0x60, 0xc0, 0x29, 0x23, 0xab, 0x0d, 0x53, 0x4e, 0x6f,
|
||||||
|
0xd5, 0xdb, 0x37, 0x45, 0xde, 0xfd, 0x8e, 0x2f, 0x03, 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b, 0x51,
|
||||||
|
0x8d, 0x1b, 0xaf, 0x92, 0xbb, 0xdd, 0xbc, 0x7f, 0x11, 0xd9, 0x5c, 0x41, 0x1f, 0x10, 0x5a, 0xd8,
|
||||||
|
0x0a, 0xc1, 0x31, 0x88, 0xa5, 0xcd, 0x7b, 0xbd, 0x2d, 0x74, 0xd0, 0x12, 0xb8, 0xe5, 0xb4, 0xb0,
|
||||||
|
0x89, 0x69, 0x97, 0x4a, 0x0c, 0x96, 0x77, 0x7e, 0x65, 0xb9, 0xf1, 0x09, 0xc5, 0x6e, 0xc6, 0x84,
|
||||||
|
0x18, 0xf0, 0x7d, 0xec, 0x3a, 0xdc, 0x4d, 0x20, 0x79, 0xee, 0x5f, 0x3e, 0xd7, 0xcb, 0x39, 0x48
|
||||||
|
];
|
||||||
|
|
||||||
|
/** "Fixed parameter CK" used in key expansion */
|
||||||
|
const CK = [
|
||||||
|
0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,
|
||||||
|
0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
|
||||||
|
0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,
|
||||||
|
0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
|
||||||
|
0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,
|
||||||
|
0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
|
||||||
|
0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,
|
||||||
|
0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279
|
||||||
|
];
|
||||||
|
|
||||||
|
/** "System parameter FK" */
|
||||||
|
const FK = [0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotating 32-bit shift left
|
||||||
|
*
|
||||||
|
* (Note that although JS integers are stored in doubles and thus have 53 bits,
|
||||||
|
* the JS bitwise operations are 32-bit)
|
||||||
|
*/
|
||||||
|
function ROL(i, n) {
|
||||||
|
return (i << n) | (i >>> (32 - n));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Linear transformation L
|
||||||
|
*
|
||||||
|
* @param {integer} b - a 32 bit integer
|
||||||
|
*/
|
||||||
|
function transformL(b) {
|
||||||
|
/* Replace each of the 4 bytes in b with the value at its offset in the Sbox */
|
||||||
|
b = (Sbox[(b >>> 24) & 0xFF] << 24) | (Sbox[(b >>> 16) & 0xFF] << 16) |
|
||||||
|
(Sbox[(b >>> 8) & 0xFF] << 8) | Sbox[b & 0xFF];
|
||||||
|
/* circular rotate and xor */
|
||||||
|
return b ^ ROL(b, 2) ^ ROL(b, 10) ^ ROL(b, 18) ^ ROL(b, 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Linear transformation L'
|
||||||
|
*
|
||||||
|
* @param {integer} b - a 32 bit integer
|
||||||
|
*/
|
||||||
|
function transformLprime(b) {
|
||||||
|
/* Replace each of the 4 bytes in b with the value at its offset in the Sbox */
|
||||||
|
b = (Sbox[(b >>> 24) & 0xFF] << 24) | (Sbox[(b >>> 16) & 0xFF] << 16) |
|
||||||
|
(Sbox[(b >>> 8) & 0xFF] << 8) | Sbox[b & 0xFF];
|
||||||
|
return b ^ ROL(b, 13) ^ ROL(b, 23); /* circular rotate and XOR */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the round key
|
||||||
|
*/
|
||||||
|
function initSM4RoundKey(rawkey) {
|
||||||
|
const K = rawkey.map((a, i) => a ^ FK[i]); /* K = rawkey ^ FK */
|
||||||
|
const roundKey = [];
|
||||||
|
for (let i = 0; i < 32; i++)
|
||||||
|
roundKey[i] = K[i + 4] = K[i] ^ transformLprime(K[i + 1] ^ K[i + 2] ^ K[i + 3] ^ CK[i]);
|
||||||
|
return roundKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts/decrypts a single block X (4 32-bit values) with a prepared round key.
|
||||||
|
*
|
||||||
|
* @param {intArray} X - A cleartext block.
|
||||||
|
* @param {intArray} roundKey - The round key from initSMRoundKey for encrypting (reversed for decrypting).
|
||||||
|
* @returns {byteArray} - The cipher text.
|
||||||
|
*/
|
||||||
|
function encryptBlockSM4(X, roundKey) {
|
||||||
|
for (let i = 0; i < NROUNDS; i++)
|
||||||
|
X[i + 4] = X[i] ^ transformL(X[i + 1] ^ X[i + 2] ^ X[i + 3] ^ roundKey[i]);
|
||||||
|
return [X[35], X[34], X[33], X[32]];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes 16 bytes from an offset in an array and returns an array of 4 32-bit Big-Endian values.
|
||||||
|
* (DataView won't work portably here as we need Big-Endian)
|
||||||
|
*
|
||||||
|
* @param {byteArray} bArray - the array of bytes
|
||||||
|
* @param {integer} offset - starting offset in the array; 15 bytes must follow it.
|
||||||
|
*/
|
||||||
|
function bytesToInts(bArray, offs=0) {
|
||||||
|
let offset = offs;
|
||||||
|
const A = (bArray[offset] << 24) | (bArray[offset + 1] << 16) | (bArray[offset + 2] << 8) | bArray[offset + 3];
|
||||||
|
offset += 4;
|
||||||
|
const B = (bArray[offset] << 24) | (bArray[offset + 1] << 16) | (bArray[offset + 2] << 8) | bArray[offset + 3];
|
||||||
|
offset += 4;
|
||||||
|
const C = (bArray[offset] << 24) | (bArray[offset + 1] << 16) | (bArray[offset + 2] << 8) | bArray[offset + 3];
|
||||||
|
offset += 4;
|
||||||
|
const D = (bArray[offset] << 24) | (bArray[offset + 1] << 16) | (bArray[offset + 2] << 8) | bArray[offset + 3];
|
||||||
|
return [A, B, C, D];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inverse of bytesToInts above; takes an array of 32-bit integers and turns it into an array of bytes.
|
||||||
|
* Again, Big-Endian order.
|
||||||
|
*/
|
||||||
|
function intsToBytes(ints) {
|
||||||
|
const bArr = [];
|
||||||
|
for (let i = 0; i < ints.length; i++) {
|
||||||
|
bArr.push((ints[i] >> 24) & 0xFF);
|
||||||
|
bArr.push((ints[i] >> 16) & 0xFF);
|
||||||
|
bArr.push((ints[i] >> 8) & 0xFF);
|
||||||
|
bArr.push(ints[i] & 0xFF);
|
||||||
|
}
|
||||||
|
return bArr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt using SM4 using a given block cipher mode.
|
||||||
|
*
|
||||||
|
* @param {byteArray} message - The clear text message; any length under 32 Gb or so.
|
||||||
|
* @param {byteArray} key - The cipher key, 16 bytes.
|
||||||
|
* @param {byteArray} iv - The IV or nonce, 16 bytes (not used with ECB mode)
|
||||||
|
* @param {string} mode - The block cipher mode "CBC", "ECB", "CFB", "OFB", "CTR".
|
||||||
|
* @param {boolean} noPadding - Don't add PKCS#7 padding if set.
|
||||||
|
* @returns {byteArray} - The cipher text.
|
||||||
|
*/
|
||||||
|
export function encryptSM4(message, key, iv, mode="ECB", noPadding=false) {
|
||||||
|
const messageLength = message.length;
|
||||||
|
if (messageLength === 0)
|
||||||
|
return [];
|
||||||
|
const roundKey = initSM4RoundKey(bytesToInts(key, 0));
|
||||||
|
|
||||||
|
/* Pad with PKCS#7 if requested for ECB/CBC else add zeroes (which are sliced off at the end) */
|
||||||
|
let padByte = 0;
|
||||||
|
let nPadding = 16 - (message.length & 0xF);
|
||||||
|
if (mode === "ECB" || mode === "CBC") {
|
||||||
|
if (noPadding) {
|
||||||
|
if (nPadding !== 16)
|
||||||
|
throw new OperationError(`No padding requested in ${mode} mode but input is not a 16-byte multiple.`);
|
||||||
|
nPadding = 0;
|
||||||
|
} else
|
||||||
|
padByte = nPadding;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < nPadding; i++)
|
||||||
|
message.push(padByte);
|
||||||
|
|
||||||
|
const cipherText = [];
|
||||||
|
switch (mode) {
|
||||||
|
case "ECB":
|
||||||
|
for (let i = 0; i < message.length; i += BLOCKSIZE)
|
||||||
|
Array.prototype.push.apply(cipherText, intsToBytes(encryptBlockSM4(bytesToInts(message, i), roundKey)));
|
||||||
|
break;
|
||||||
|
case "CBC":
|
||||||
|
iv = bytesToInts(iv, 0);
|
||||||
|
for (let i = 0; i < message.length; i += BLOCKSIZE) {
|
||||||
|
const block = bytesToInts(message, i);
|
||||||
|
block[0] ^= iv[0]; block[1] ^= iv[1];
|
||||||
|
block[2] ^= iv[2]; block[3] ^= iv[3];
|
||||||
|
iv = encryptBlockSM4(block, roundKey);
|
||||||
|
Array.prototype.push.apply(cipherText, intsToBytes(iv));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "CFB":
|
||||||
|
iv = bytesToInts(iv, 0);
|
||||||
|
for (let i = 0; i < message.length; i += BLOCKSIZE) {
|
||||||
|
iv = encryptBlockSM4(iv, roundKey);
|
||||||
|
const block = bytesToInts(message, i);
|
||||||
|
block[0] ^= iv[0]; block[1] ^= iv[1];
|
||||||
|
block[2] ^= iv[2]; block[3] ^= iv[3];
|
||||||
|
Array.prototype.push.apply(cipherText, intsToBytes(block));
|
||||||
|
iv = block;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "OFB":
|
||||||
|
iv = bytesToInts(iv, 0);
|
||||||
|
for (let i = 0; i < message.length; i += BLOCKSIZE) {
|
||||||
|
iv = encryptBlockSM4(iv, roundKey);
|
||||||
|
const block = bytesToInts(message, i);
|
||||||
|
block[0] ^= iv[0]; block[1] ^= iv[1];
|
||||||
|
block[2] ^= iv[2]; block[3] ^= iv[3];
|
||||||
|
Array.prototype.push.apply(cipherText, intsToBytes(block));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "CTR":
|
||||||
|
iv = bytesToInts(iv, 0);
|
||||||
|
for (let i = 0; i < message.length; i += BLOCKSIZE) {
|
||||||
|
let iv2 = [...iv]; /* containing the IV + counter */
|
||||||
|
iv2[3] += (i >> 4);/* Using a 32 bit counter here. 64 Gb encrypts should be enough for everyone. */
|
||||||
|
iv2 = encryptBlockSM4(iv2, roundKey);
|
||||||
|
const block = bytesToInts(message, i);
|
||||||
|
block[0] ^= iv2[0]; block[1] ^= iv2[1];
|
||||||
|
block[2] ^= iv2[2]; block[3] ^= iv2[3];
|
||||||
|
Array.prototype.push.apply(cipherText, intsToBytes(block));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new OperationError("Invalid block cipher mode: "+mode);
|
||||||
|
}
|
||||||
|
if (mode !== "ECB" && mode !== "CBC")
|
||||||
|
return cipherText.slice(0, messageLength);
|
||||||
|
return cipherText;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt using SM4 using a given block cipher mode.
|
||||||
|
*
|
||||||
|
* @param {byteArray} cipherText - The ciphertext
|
||||||
|
* @param {byteArray} key - The cipher key, 16 bytes.
|
||||||
|
* @param {byteArray} iv - The IV or nonce, 16 bytes (not used with ECB mode)
|
||||||
|
* @param {string} mode - The block cipher mode "CBC", "ECB", "CFB", "OFB", "CTR"
|
||||||
|
* @param {boolean] ignorePadding - If true, ignore padding issues in ECB/CBC mode.
|
||||||
|
* @returns {byteArray} - The cipher text.
|
||||||
|
*/
|
||||||
|
export function decryptSM4(cipherText, key, iv, mode="ECB", ignorePadding=false) {
|
||||||
|
const originalLength = cipherText.length;
|
||||||
|
if (originalLength === 0)
|
||||||
|
return [];
|
||||||
|
let roundKey = initSM4RoundKey(bytesToInts(key, 0));
|
||||||
|
|
||||||
|
if (mode === "ECB" || mode === "CBC") {
|
||||||
|
/* Init decryption key */
|
||||||
|
roundKey = roundKey.reverse();
|
||||||
|
if ((originalLength & 0xF) !== 0 && !ignorePadding)
|
||||||
|
throw new OperationError(`With ECB or CBC modes, the input must be divisible into 16 byte blocks. (${cipherText.length & 0xF} bytes extra)`);
|
||||||
|
} else { /* Pad dummy bytes for other modes, chop them off at the end */
|
||||||
|
while ((cipherText.length & 0xF) !== 0)
|
||||||
|
cipherText.push(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearText = [];
|
||||||
|
switch (mode) {
|
||||||
|
case "ECB":
|
||||||
|
for (let i = 0; i < cipherText.length; i += BLOCKSIZE)
|
||||||
|
Array.prototype.push.apply(clearText, intsToBytes(encryptBlockSM4(bytesToInts(cipherText, i), roundKey)));
|
||||||
|
break;
|
||||||
|
case "CBC":
|
||||||
|
iv = bytesToInts(iv, 0);
|
||||||
|
for (let i = 0; i < cipherText.length; i += BLOCKSIZE) {
|
||||||
|
const block = encryptBlockSM4(bytesToInts(cipherText, i), roundKey);
|
||||||
|
block[0] ^= iv[0]; block[1] ^= iv[1];
|
||||||
|
block[2] ^= iv[2]; block[3] ^= iv[3];
|
||||||
|
Array.prototype.push.apply(clearText, intsToBytes(block));
|
||||||
|
iv = bytesToInts(cipherText, i);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "CFB":
|
||||||
|
iv = bytesToInts(iv, 0);
|
||||||
|
for (let i = 0; i < cipherText.length; i += BLOCKSIZE) {
|
||||||
|
iv = encryptBlockSM4(iv, roundKey);
|
||||||
|
const block = bytesToInts(cipherText, i);
|
||||||
|
block[0] ^= iv[0]; block[1] ^= iv[1];
|
||||||
|
block[2] ^= iv[2]; block[3] ^= iv[3];
|
||||||
|
Array.prototype.push.apply(clearText, intsToBytes(block));
|
||||||
|
iv = bytesToInts(cipherText, i);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "OFB":
|
||||||
|
iv = bytesToInts(iv, 0);
|
||||||
|
for (let i = 0; i < cipherText.length; i += BLOCKSIZE) {
|
||||||
|
iv = encryptBlockSM4(iv, roundKey);
|
||||||
|
const block = bytesToInts(cipherText, i);
|
||||||
|
block[0] ^= iv[0]; block[1] ^= iv[1];
|
||||||
|
block[2] ^= iv[2]; block[3] ^= iv[3];
|
||||||
|
Array.prototype.push.apply(clearText, intsToBytes(block));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "CTR":
|
||||||
|
iv = bytesToInts(iv, 0);
|
||||||
|
for (let i = 0; i < cipherText.length; i += BLOCKSIZE) {
|
||||||
|
let iv2 = [...iv]; /* containing the IV + counter */
|
||||||
|
iv2[3] += (i >> 4);/* Using a 32 bit counter here. 64 Gb encrypts should be enough for everyone. */
|
||||||
|
iv2 = encryptBlockSM4(iv2, roundKey);
|
||||||
|
const block = bytesToInts(cipherText, i);
|
||||||
|
block[0] ^= iv2[0]; block[1] ^= iv2[1];
|
||||||
|
block[2] ^= iv2[2]; block[3] ^= iv2[3];
|
||||||
|
Array.prototype.push.apply(clearText, intsToBytes(block));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new OperationError(`Invalid block cipher mode: ${mode}`);
|
||||||
|
}
|
||||||
|
/* Check PKCS#7 padding */
|
||||||
|
if (mode === "ECB" || mode === "CBC") {
|
||||||
|
if (ignorePadding)
|
||||||
|
return clearText;
|
||||||
|
const padByte = clearText[clearText.length - 1];
|
||||||
|
if (padByte > 16)
|
||||||
|
throw new OperationError("Invalid PKCS#7 padding.");
|
||||||
|
for (let i = 0; i < padByte; i++)
|
||||||
|
if (clearText[clearText.length -i - 1] !== padByte)
|
||||||
|
throw new OperationError("Invalid PKCS#7 padding.");
|
||||||
|
return clearText.slice(0, clearText.length - padByte);
|
||||||
|
}
|
||||||
|
return clearText.slice(0, originalLength);
|
||||||
|
}
|
||||||
|
|
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;
|
||||||
|
}
|
117
src/core/lib/Sort.mjs
Normal file
117
src/core/lib/Sort.mjs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/**
|
||||||
|
* Sorting functions
|
||||||
|
*
|
||||||
|
* @author n1474335 [n1474335@gmail.com]
|
||||||
|
* @copyright Crown Copyright 2022
|
||||||
|
* @license Apache-2.0
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparison operation for sorting of strings ignoring case.
|
||||||
|
*
|
||||||
|
* @param {string} a
|
||||||
|
* @param {string} b
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
export function caseInsensitiveSort(a, b) {
|
||||||
|
return a.toLowerCase().localeCompare(b.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparison operation for sorting of IPv4 addresses.
|
||||||
|
*
|
||||||
|
* @param {string} a
|
||||||
|
* @param {string} b
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
export function ipSort(a, b) {
|
||||||
|
let a_ = a.split("."),
|
||||||
|
b_ = b.split(".");
|
||||||
|
|
||||||
|
a_ = a_[0] * 0x1000000 + a_[1] * 0x10000 + a_[2] * 0x100 + a_[3] * 1;
|
||||||
|
b_ = b_[0] * 0x1000000 + b_[1] * 0x10000 + b_[2] * 0x100 + b_[3] * 1;
|
||||||
|
|
||||||
|
if (isNaN(a_) && !isNaN(b_)) return 1;
|
||||||
|
if (!isNaN(a_) && isNaN(b_)) return -1;
|
||||||
|
if (isNaN(a_) && isNaN(b_)) return a.localeCompare(b);
|
||||||
|
|
||||||
|
return a_ - b_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparison operation for sorting of numeric values.
|
||||||
|
*
|
||||||
|
* @author Chris van Marle
|
||||||
|
* @param {string} a
|
||||||
|
* @param {string} b
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
export function numericSort(a, b) {
|
||||||
|
const a_ = a.split(/([^\d]+)/),
|
||||||
|
b_ = b.split(/([^\d]+)/);
|
||||||
|
|
||||||
|
for (let i = 0; i < a_.length && i < b.length; ++i) {
|
||||||
|
if (isNaN(a_[i]) && !isNaN(b_[i])) return 1; // Numbers after non-numbers
|
||||||
|
if (!isNaN(a_[i]) && isNaN(b_[i])) return -1;
|
||||||
|
if (isNaN(a_[i]) && isNaN(b_[i])) {
|
||||||
|
const ret = a_[i].localeCompare(b_[i]); // Compare strings
|
||||||
|
if (ret !== 0) return ret;
|
||||||
|
}
|
||||||
|
if (!isNaN(a_[i]) && !isNaN(b_[i])) { // Compare numbers
|
||||||
|
if (a_[i] - b_[i] !== 0) return a_[i] - b_[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.localeCompare(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparison operation for sorting of hexadecimal values.
|
||||||
|
*
|
||||||
|
* @author Chris van Marle
|
||||||
|
* @param {string} a
|
||||||
|
* @param {string} b
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
export function hexadecimalSort(a, b) {
|
||||||
|
let a_ = a.split(/([^\da-f]+)/i),
|
||||||
|
b_ = b.split(/([^\da-f]+)/i);
|
||||||
|
|
||||||
|
a_ = a_.map(v => {
|
||||||
|
const t = parseInt(v, 16);
|
||||||
|
return isNaN(t) ? v : t;
|
||||||
|
});
|
||||||
|
|
||||||
|
b_ = b_.map(v => {
|
||||||
|
const t = parseInt(v, 16);
|
||||||
|
return isNaN(t) ? v : t;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 0; i < a_.length && i < b.length; ++i) {
|
||||||
|
if (isNaN(a_[i]) && !isNaN(b_[i])) return 1; // Numbers after non-numbers
|
||||||
|
if (!isNaN(a_[i]) && isNaN(b_[i])) return -1;
|
||||||
|
if (isNaN(a_[i]) && isNaN(b_[i])) {
|
||||||
|
const ret = a_[i].localeCompare(b_[i]); // Compare strings
|
||||||
|
if (ret !== 0) return ret;
|
||||||
|
}
|
||||||
|
if (!isNaN(a_[i]) && !isNaN(b_[i])) { // Compare numbers
|
||||||
|
if (a_[i] - b_[i] !== 0) return a_[i] - b_[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.localeCompare(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparison operation for sorting by length
|
||||||
|
*
|
||||||
|
* @param {string} a
|
||||||
|
* @param {string} b
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
export function lengthSort(a, b) {
|
||||||
|
return a.length - b.length;
|
||||||
|
}
|
||||||
|
|
@ -18,24 +18,37 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a number of bytes from the current position.
|
* Clone this Stream returning a new identical Stream.
|
||||||
*
|
*
|
||||||
* @param {number} numBytes
|
* @returns {Stream}
|
||||||
|
*/
|
||||||
|
clone() {
|
||||||
|
return new Stream(this.bytes, this.position, this.bitPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a number of bytes from the current position, or all remaining bytes.
|
||||||
|
*
|
||||||
|
* @param {number} [numBytes=null]
|
||||||
* @returns {Uint8Array}
|
* @returns {Uint8Array}
|
||||||
*/
|
*/
|
||||||
getBytes(numBytes) {
|
getBytes(numBytes=null) {
|
||||||
if (this.position > this.length) return undefined;
|
if (this.position > this.length) return undefined;
|
||||||
|
|
||||||
const newPosition = this.position + numBytes;
|
const newPosition = numBytes !== null ?
|
||||||
|
this.position + numBytes :
|
||||||
|
this.length;
|
||||||
const bytes = this.bytes.slice(this.position, newPosition);
|
const bytes = this.bytes.slice(this.position, newPosition);
|
||||||
this.position = newPosition;
|
this.position = newPosition;
|
||||||
this.bitPos = 0;
|
this.bitPos = 0;
|
||||||
@ -46,12 +59,14 @@ export default class Stream {
|
|||||||
* Interpret the following bytes as a string, stopping at the next null byte or
|
* Interpret the following bytes as a string, stopping at the next null byte or
|
||||||
* the supplied limit.
|
* the supplied limit.
|
||||||
*
|
*
|
||||||
* @param {number} numBytes
|
* @param {number} [numBytes=-1]
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
readString(numBytes) {
|
readString(numBytes=-1) {
|
||||||
if (this.position > this.length) return undefined;
|
if (this.position > this.length) return undefined;
|
||||||
|
|
||||||
|
if (numBytes === -1) numBytes = this.length - this.position;
|
||||||
|
|
||||||
let result = "";
|
let result = "";
|
||||||
for (let i = this.position; i < this.position + numBytes; i++) {
|
for (let i = this.position; i < this.position + numBytes; i++) {
|
||||||
const currentByte = this.bytes[i];
|
const currentByte = this.bytes[i];
|
||||||
@ -91,34 +106,40 @@ export default class Stream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a number of bits from the buffer.
|
* Reads a number of bits from the buffer in big or little endian.
|
||||||
*
|
|
||||||
* @TODO Add endianness
|
|
||||||
*
|
*
|
||||||
* @param {number} numBits
|
* @param {number} numBits
|
||||||
|
* @param {string} [endianness="be"]
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
readBits(numBits) {
|
readBits(numBits, endianness="be") {
|
||||||
if (this.position > this.length) return undefined;
|
if (this.position > this.length) return undefined;
|
||||||
|
|
||||||
let bitBuf = 0,
|
let bitBuf = 0,
|
||||||
bitBufLen = 0;
|
bitBufLen = 0;
|
||||||
|
|
||||||
// Add remaining bits from current byte
|
// Add remaining bits from current byte
|
||||||
bitBuf = (this.bytes[this.position++] & bitMask(this.bitPos)) >>> this.bitPos;
|
bitBuf = this.bytes[this.position++] & bitMask(this.bitPos);
|
||||||
|
if (endianness !== "be") bitBuf >>>= this.bitPos;
|
||||||
bitBufLen = 8 - this.bitPos;
|
bitBufLen = 8 - this.bitPos;
|
||||||
this.bitPos = 0;
|
this.bitPos = 0;
|
||||||
|
|
||||||
// Not enough bits yet
|
// Not enough bits yet
|
||||||
while (bitBufLen < numBits) {
|
while (bitBufLen < numBits) {
|
||||||
bitBuf |= this.bytes[this.position++] << bitBufLen;
|
if (endianness === "be")
|
||||||
|
bitBuf = (bitBuf << bitBufLen) | this.bytes[this.position++];
|
||||||
|
else
|
||||||
|
bitBuf |= this.bytes[this.position++] << bitBufLen;
|
||||||
bitBufLen += 8;
|
bitBufLen += 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reverse back to numBits
|
// Reverse back to numBits
|
||||||
if (bitBufLen > numBits) {
|
if (bitBufLen > numBits) {
|
||||||
const excess = bitBufLen - numBits;
|
const excess = bitBufLen - numBits;
|
||||||
bitBuf &= (1 << numBits) - 1;
|
if (endianness === "be")
|
||||||
|
bitBuf >>>= excess;
|
||||||
|
else
|
||||||
|
bitBuf &= (1 << numBits) - 1;
|
||||||
bitBufLen -= excess;
|
bitBufLen -= excess;
|
||||||
this.position--;
|
this.position--;
|
||||||
this.bitPos = 8 - excess;
|
this.bitPos = 8 - excess;
|
||||||
@ -133,7 +154,9 @@ export default class Stream {
|
|||||||
* @returns {number} The bit mask
|
* @returns {number} The bit mask
|
||||||
*/
|
*/
|
||||||
function bitMask(bitPos) {
|
function bitMask(bitPos) {
|
||||||
return 256 - (1 << bitPos);
|
return endianness === "be" ?
|
||||||
|
(1 << (8 - bitPos)) - 1 :
|
||||||
|
256 - (1 << 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,7 @@ class AESDecrypt extends Operation {
|
|||||||
|
|
||||||
this.name = "AES Decrypt";
|
this.name = "AES Decrypt";
|
||||||
this.module = "Ciphers";
|
this.module = "Ciphers";
|
||||||
this.description = "Advanced Encryption Standard (AES) is a U.S. Federal Information Processing Standard (FIPS). It was selected after a 5-year process where 15 competing designs were evaluated.<br><br><b>Key:</b> The following algorithms will be used based on the size of the key:<ul><li>16 bytes = AES-128</li><li>24 bytes = AES-192</li><li>32 bytes = AES-256</li></ul><br><br><b>IV:</b> The Initialization Vector should be 16 bytes long. If not entered, it will default to 16 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.<br><br><b>GCM Tag:</b> This field is ignored unless 'GCM' mode is used.";
|
this.description = "Advanced Encryption Standard (AES) is a U.S. Federal Information Processing Standard (FIPS). It was selected after a 5-year process where 15 competing designs were evaluated.<br><br><b>Key:</b> The following algorithms will be used based on the size of the key:<ul><li>16 bytes = AES-128</li><li>24 bytes = AES-192</li><li>32 bytes = AES-256</li></ul><br><br><b>IV:</b> The Initialization Vector should be 16 bytes long. If not entered, it will default to 16 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used as a default.<br><br><b>GCM Tag:</b> This field is ignored unless 'GCM' mode is used.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Advanced_Encryption_Standard";
|
this.infoURL = "https://wikipedia.org/wiki/Advanced_Encryption_Standard";
|
||||||
this.inputType = "string";
|
this.inputType = "string";
|
||||||
this.outputType = "string";
|
this.outputType = "string";
|
||||||
@ -66,6 +66,14 @@ class AESDecrypt extends Operation {
|
|||||||
{
|
{
|
||||||
name: "ECB",
|
name: "ECB",
|
||||||
off: [5, 6]
|
off: [5, 6]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CBC/NoPadding",
|
||||||
|
off: [5, 6]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ECB/NoPadding",
|
||||||
|
off: [5, 6]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -104,7 +112,8 @@ class AESDecrypt extends Operation {
|
|||||||
run(input, args) {
|
run(input, args) {
|
||||||
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
||||||
iv = Utils.convertToByteString(args[1].string, args[1].option),
|
iv = Utils.convertToByteString(args[1].string, args[1].option),
|
||||||
mode = args[2],
|
mode = args[2].substring(0, 3),
|
||||||
|
noPadding = args[2].endsWith("NoPadding"),
|
||||||
inputType = args[3],
|
inputType = args[3],
|
||||||
outputType = args[4],
|
outputType = args[4],
|
||||||
gcmTag = Utils.convertToByteString(args[5].string, args[5].option),
|
gcmTag = Utils.convertToByteString(args[5].string, args[5].option),
|
||||||
@ -122,6 +131,14 @@ The following algorithms will be used based on the size of the key:
|
|||||||
input = Utils.convertToByteString(input, inputType);
|
input = Utils.convertToByteString(input, inputType);
|
||||||
|
|
||||||
const decipher = forge.cipher.createDecipher("AES-" + mode, key);
|
const decipher = forge.cipher.createDecipher("AES-" + mode, key);
|
||||||
|
|
||||||
|
/* Allow for a "no padding" mode */
|
||||||
|
if (noPadding) {
|
||||||
|
decipher.mode.unpad = function(output, options) {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
decipher.start({
|
decipher.start({
|
||||||
iv: iv.length === 0 ? "" : iv,
|
iv: iv.length === 0 ? "" : iv,
|
||||||
tag: mode === "GCM" ? gcmTag : undefined,
|
tag: mode === "GCM" ? gcmTag : undefined,
|
||||||
|
128
src/core/operations/AESKeyUnwrap.mjs
Normal file
128
src/core/operations/AESKeyUnwrap.mjs
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
/**
|
||||||
|
* @author mikecat
|
||||||
|
* @copyright Crown Copyright 2022
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
import { toHexFast } from "../lib/Hex.mjs";
|
||||||
|
import forge from "node-forge";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES Key Unwrap operation
|
||||||
|
*/
|
||||||
|
class AESKeyUnwrap extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AESKeyUnwrap constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "AES Key Unwrap";
|
||||||
|
this.module = "Ciphers";
|
||||||
|
this.description = "Decryptor for a key wrapping algorithm defined in RFC3394, which is used to protect keys in untrusted storage or communications, using AES.<br><br>This algorithm uses an AES key (KEK: key-encryption key) and a 64-bit IV to decrypt 64-bit blocks.";
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/Key_wrap";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Key (KEK)",
|
||||||
|
"type": "toggleString",
|
||||||
|
"value": "",
|
||||||
|
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "IV",
|
||||||
|
"type": "toggleString",
|
||||||
|
"value": "a6a6a6a6a6a6a6a6",
|
||||||
|
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Input",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["Hex", "Raw"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Output",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["Hex", "Raw"]
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const kek = Utils.convertToByteString(args[0].string, args[0].option),
|
||||||
|
iv = Utils.convertToByteString(args[1].string, args[1].option),
|
||||||
|
inputType = args[2],
|
||||||
|
outputType = args[3];
|
||||||
|
|
||||||
|
if (kek.length !== 16 && kek.length !== 24 && kek.length !== 32) {
|
||||||
|
throw new OperationError("KEK must be either 16, 24, or 32 bytes (currently " + kek.length + " bytes)");
|
||||||
|
}
|
||||||
|
if (iv.length !== 8) {
|
||||||
|
throw new OperationError("IV must be 8 bytes (currently " + iv.length + " bytes)");
|
||||||
|
}
|
||||||
|
const inputData = Utils.convertToByteString(input, inputType);
|
||||||
|
if (inputData.length % 8 !== 0 || inputData.length < 24) {
|
||||||
|
throw new OperationError("input must be 8n (n>=3) bytes (currently " + inputData.length + " bytes)");
|
||||||
|
}
|
||||||
|
|
||||||
|
const cipher = forge.cipher.createCipher("AES-ECB", kek);
|
||||||
|
cipher.start();
|
||||||
|
cipher.update(forge.util.createBuffer(""));
|
||||||
|
cipher.finish();
|
||||||
|
const paddingBlock = cipher.output.getBytes();
|
||||||
|
|
||||||
|
const decipher = forge.cipher.createDecipher("AES-ECB", kek);
|
||||||
|
|
||||||
|
let A = inputData.substring(0, 8);
|
||||||
|
const R = [];
|
||||||
|
for (let i = 8; i < inputData.length; i += 8) {
|
||||||
|
R.push(inputData.substring(i, i + 8));
|
||||||
|
}
|
||||||
|
let cntLower = R.length >>> 0;
|
||||||
|
let cntUpper = (R.length / ((1 << 30) * 4)) >>> 0;
|
||||||
|
cntUpper = cntUpper * 6 + ((cntLower * 6 / ((1 << 30) * 4)) >>> 0);
|
||||||
|
cntLower = cntLower * 6 >>> 0;
|
||||||
|
for (let j = 5; j >= 0; j--) {
|
||||||
|
for (let i = R.length - 1; i >= 0; i--) {
|
||||||
|
const aBuffer = Utils.strToArrayBuffer(A);
|
||||||
|
const aView = new DataView(aBuffer);
|
||||||
|
aView.setUint32(0, aView.getUint32(0) ^ cntUpper);
|
||||||
|
aView.setUint32(4, aView.getUint32(4) ^ cntLower);
|
||||||
|
A = Utils.arrayBufferToStr(aBuffer, false);
|
||||||
|
decipher.start();
|
||||||
|
decipher.update(forge.util.createBuffer(A + R[i] + paddingBlock));
|
||||||
|
decipher.finish();
|
||||||
|
const B = decipher.output.getBytes();
|
||||||
|
A = B.substring(0, 8);
|
||||||
|
R[i] = B.substring(8, 16);
|
||||||
|
cntLower--;
|
||||||
|
if (cntLower < 0) {
|
||||||
|
cntUpper--;
|
||||||
|
cntLower = 0xffffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (A !== iv) {
|
||||||
|
throw new OperationError("IV mismatch");
|
||||||
|
}
|
||||||
|
const P = R.join("");
|
||||||
|
|
||||||
|
if (outputType === "Hex") {
|
||||||
|
return toHexFast(Utils.strToArrayBuffer(P));
|
||||||
|
}
|
||||||
|
return P;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AESKeyUnwrap;
|
115
src/core/operations/AESKeyWrap.mjs
Normal file
115
src/core/operations/AESKeyWrap.mjs
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
/**
|
||||||
|
* @author mikecat
|
||||||
|
* @copyright Crown Copyright 2022
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
import { toHexFast } from "../lib/Hex.mjs";
|
||||||
|
import forge from "node-forge";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES Key Wrap operation
|
||||||
|
*/
|
||||||
|
class AESKeyWrap extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AESKeyWrap constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "AES Key Wrap";
|
||||||
|
this.module = "Ciphers";
|
||||||
|
this.description = "A key wrapping algorithm defined in RFC3394, which is used to protect keys in untrusted storage or communications, using AES.<br><br>This algorithm uses an AES key (KEK: key-encryption key) and a 64-bit IV to encrypt 64-bit blocks.";
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/Key_wrap";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Key (KEK)",
|
||||||
|
"type": "toggleString",
|
||||||
|
"value": "",
|
||||||
|
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "IV",
|
||||||
|
"type": "toggleString",
|
||||||
|
"value": "a6a6a6a6a6a6a6a6",
|
||||||
|
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Input",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["Hex", "Raw"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Output",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["Hex", "Raw"]
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const kek = Utils.convertToByteString(args[0].string, args[0].option),
|
||||||
|
iv = Utils.convertToByteString(args[1].string, args[1].option),
|
||||||
|
inputType = args[2],
|
||||||
|
outputType = args[3];
|
||||||
|
|
||||||
|
if (kek.length !== 16 && kek.length !== 24 && kek.length !== 32) {
|
||||||
|
throw new OperationError("KEK must be either 16, 24, or 32 bytes (currently " + kek.length + " bytes)");
|
||||||
|
}
|
||||||
|
if (iv.length !== 8) {
|
||||||
|
throw new OperationError("IV must be 8 bytes (currently " + iv.length + " bytes)");
|
||||||
|
}
|
||||||
|
const inputData = Utils.convertToByteString(input, inputType);
|
||||||
|
if (inputData.length % 8 !== 0 || inputData.length < 16) {
|
||||||
|
throw new OperationError("input must be 8n (n>=2) bytes (currently " + inputData.length + " bytes)");
|
||||||
|
}
|
||||||
|
|
||||||
|
const cipher = forge.cipher.createCipher("AES-ECB", kek);
|
||||||
|
|
||||||
|
let A = iv;
|
||||||
|
const R = [];
|
||||||
|
for (let i = 0; i < inputData.length; i += 8) {
|
||||||
|
R.push(inputData.substring(i, i + 8));
|
||||||
|
}
|
||||||
|
let cntLower = 1, cntUpper = 0;
|
||||||
|
for (let j = 0; j < 6; j++) {
|
||||||
|
for (let i = 0; i < R.length; i++) {
|
||||||
|
cipher.start();
|
||||||
|
cipher.update(forge.util.createBuffer(A + R[i]));
|
||||||
|
cipher.finish();
|
||||||
|
const B = cipher.output.getBytes();
|
||||||
|
const msbBuffer = Utils.strToArrayBuffer(B.substring(0, 8));
|
||||||
|
const msbView = new DataView(msbBuffer);
|
||||||
|
msbView.setUint32(0, msbView.getUint32(0) ^ cntUpper);
|
||||||
|
msbView.setUint32(4, msbView.getUint32(4) ^ cntLower);
|
||||||
|
A = Utils.arrayBufferToStr(msbBuffer, false);
|
||||||
|
R[i] = B.substring(8, 16);
|
||||||
|
cntLower++;
|
||||||
|
if (cntLower > 0xffffffff) {
|
||||||
|
cntUpper++;
|
||||||
|
cntLower = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const C = A + R.join("");
|
||||||
|
|
||||||
|
if (outputType === "Hex") {
|
||||||
|
return toHexFast(Utils.strToArrayBuffer(C));
|
||||||
|
}
|
||||||
|
return C;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AESKeyWrap;
|
52
src/core/operations/AMFDecode.mjs
Normal file
52
src/core/operations/AMFDecode.mjs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* @author n1474335 [n1474335@gmail.com]
|
||||||
|
* @copyright Crown Copyright 2022
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import "reflect-metadata"; // Required as a shim for the amf library
|
||||||
|
import { AMF0, AMF3 } from "@astronautlabs/amf";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AMF Decode operation
|
||||||
|
*/
|
||||||
|
class AMFDecode extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AMFDecode constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "AMF Decode";
|
||||||
|
this.module = "Encodings";
|
||||||
|
this.description = "Action Message Format (AMF) is a binary format used to serialize object graphs such as ActionScript objects and XML, or send messages between an Adobe Flash client and a remote service, usually a Flash Media Server or third party alternatives.";
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/Action_Message_Format";
|
||||||
|
this.inputType = "ArrayBuffer";
|
||||||
|
this.outputType = "JSON";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
name: "Format",
|
||||||
|
type: "option",
|
||||||
|
value: ["AMF0", "AMF3"],
|
||||||
|
defaultIndex: 1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ArrayBuffer} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {JSON}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const [format] = args;
|
||||||
|
const handler = format === "AMF0" ? AMF0 : AMF3;
|
||||||
|
const encoded = new Uint8Array(input);
|
||||||
|
return handler.Value.deserialize(encoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AMFDecode;
|
52
src/core/operations/AMFEncode.mjs
Normal file
52
src/core/operations/AMFEncode.mjs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* @author n1474335 [n1474335@gmail.com]
|
||||||
|
* @copyright Crown Copyright 2022
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import "reflect-metadata"; // Required as a shim for the amf library
|
||||||
|
import { AMF0, AMF3 } from "@astronautlabs/amf";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AMF Encode operation
|
||||||
|
*/
|
||||||
|
class AMFEncode extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AMFEncode constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "AMF Encode";
|
||||||
|
this.module = "Encodings";
|
||||||
|
this.description = "Action Message Format (AMF) is a binary format used to serialize object graphs such as ActionScript objects and XML, or send messages between an Adobe Flash client and a remote service, usually a Flash Media Server or third party alternatives.";
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/Action_Message_Format";
|
||||||
|
this.inputType = "JSON";
|
||||||
|
this.outputType = "ArrayBuffer";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
name: "Format",
|
||||||
|
type: "option",
|
||||||
|
value: ["AMF0", "AMF3"],
|
||||||
|
defaultIndex: 1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {JSON} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {ArrayBuffer}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const [format] = args;
|
||||||
|
const handler = format === "AMF0" ? AMF0 : AMF3;
|
||||||
|
const output = handler.Value.any(input).serialize();
|
||||||
|
return output.buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AMFEncode;
|
@ -9,8 +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 jimplib from "jimp/es/index.js";
|
import Jimp from "jimp/es/index.js";
|
||||||
const jimp = jimplib.default ? jimplib.default : jimplib;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add Text To Image operation
|
* Add Text To Image operation
|
||||||
@ -128,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})`);
|
||||||
}
|
}
|
||||||
@ -164,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) {
|
||||||
@ -191,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
|
||||||
@ -199,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,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) {
|
||||||
|
117
src/core/operations/Argon2.mjs
Normal file
117
src/core/operations/Argon2.mjs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/**
|
||||||
|
* @author Tan Zhen Yong [tzy@beyondthesprawl.com]
|
||||||
|
* @copyright Crown Copyright 2019
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
import argon2 from "argon2-browser";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Argon2 operation
|
||||||
|
*/
|
||||||
|
class Argon2 extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Argon2 constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Argon2";
|
||||||
|
this.module = "Crypto";
|
||||||
|
this.description = "Argon2 is a key derivation function that was selected as the winner of the Password Hashing Competition in July 2015. It was designed by Alex Biryukov, Daniel Dinu, and Dmitry Khovratovich from the University of Luxembourg.<br><br>Enter the password in the input to generate its hash.";
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/Argon2";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Salt",
|
||||||
|
"type": "toggleString",
|
||||||
|
"value": "somesalt",
|
||||||
|
"toggleValues": ["UTF8", "Hex", "Base64", "Latin1"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Iterations",
|
||||||
|
"type": "number",
|
||||||
|
"value": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Memory (KiB)",
|
||||||
|
"type": "number",
|
||||||
|
"value": 4096
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Parallelism",
|
||||||
|
"type": "number",
|
||||||
|
"value": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Hash length (bytes)",
|
||||||
|
"type": "number",
|
||||||
|
"value": 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Type",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["Argon2i", "Argon2d", "Argon2id"],
|
||||||
|
"defaultIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Output format",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["Encoded hash", "Hex hash", "Raw hash"]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
async run(input, args) {
|
||||||
|
const argon2Types = {
|
||||||
|
"Argon2i": argon2.ArgonType.Argon2i,
|
||||||
|
"Argon2d": argon2.ArgonType.Argon2d,
|
||||||
|
"Argon2id": argon2.ArgonType.Argon2id
|
||||||
|
};
|
||||||
|
|
||||||
|
const salt = Utils.convertToByteString(args[0].string || "", args[0].option),
|
||||||
|
time = args[1],
|
||||||
|
mem = args[2],
|
||||||
|
parallelism = args[3],
|
||||||
|
hashLen = args[4],
|
||||||
|
type = argon2Types[args[5]],
|
||||||
|
outFormat = args[6];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await argon2.hash({
|
||||||
|
pass: input,
|
||||||
|
salt,
|
||||||
|
time,
|
||||||
|
mem,
|
||||||
|
parallelism,
|
||||||
|
hashLen,
|
||||||
|
type,
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (outFormat) {
|
||||||
|
case "Hex hash":
|
||||||
|
return result.hashHex;
|
||||||
|
case "Raw hash":
|
||||||
|
return Utils.arrayBufferToStr(result.hash);
|
||||||
|
case "Encoded hash":
|
||||||
|
default:
|
||||||
|
return result.encoded;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
throw new OperationError(`Error: ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Argon2;
|
58
src/core/operations/Argon2Compare.mjs
Normal file
58
src/core/operations/Argon2Compare.mjs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* @author Tan Zhen Yong [tzy@beyondthesprawl.com]
|
||||||
|
* @copyright Crown Copyright 2019
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import argon2 from "argon2-browser";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Argon2 compare operation
|
||||||
|
*/
|
||||||
|
class Argon2Compare extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Argon2Compare constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Argon2 compare";
|
||||||
|
this.module = "Crypto";
|
||||||
|
this.description = "Tests whether the input matches the given Argon2 hash. To test multiple possible passwords, use the 'Fork' operation.";
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/Argon2";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Encoded hash",
|
||||||
|
"type": "string",
|
||||||
|
"value": ""
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
async run(input, args) {
|
||||||
|
const encoded = args[0];
|
||||||
|
|
||||||
|
try {
|
||||||
|
await argon2.verify({
|
||||||
|
pass: input,
|
||||||
|
encoded
|
||||||
|
});
|
||||||
|
|
||||||
|
return `Match: ${input}`;
|
||||||
|
} catch (err) {
|
||||||
|
return "No match";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Argon2Compare;
|
@ -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 (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's key length needs to be between 4 and 56 bytes (32-448 bits).`);
|
||||||
|
}
|
||||||
|
|
||||||
Blowfish uses a key length of 8 bytes (64 bits).`);
|
if (iv.length !== 8) {
|
||||||
|
throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes`);
|
||||||
}
|
}
|
||||||
|
|
||||||
input = Utils.convertToByteString(input, inputType);
|
input = Utils.convertToByteString(input, inputType);
|
||||||
|
@ -10,8 +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 jimplib from "jimp/es/index.js";
|
import Jimp from "jimp/es/index.js";
|
||||||
const jimp = jimplib.default ? jimplib.default : jimplib;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blur Image operation
|
* Blur Image operation
|
||||||
@ -60,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})`);
|
||||||
}
|
}
|
||||||
@ -80,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) {
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
/**
|
/**
|
||||||
* Emulation of the Bombe machine.
|
* Emulation of the Bombe machine.
|
||||||
*
|
*
|
||||||
|
* Tested against the Bombe Rebuild at Bletchley Park's TNMOC
|
||||||
|
* using a variety of inputs and settings to confirm correctness.
|
||||||
|
*
|
||||||
* @author s2224834
|
* @author s2224834
|
||||||
* @copyright Crown Copyright 2019
|
* @copyright Crown Copyright 2019
|
||||||
* @license Apache-2.0
|
* @license Apache-2.0
|
||||||
|
149
src/core/operations/CMAC.mjs
Normal file
149
src/core/operations/CMAC.mjs
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
/**
|
||||||
|
* @author mikecat
|
||||||
|
* @copyright Crown Copyright 2022
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
import forge from "node-forge";
|
||||||
|
import { toHexFast } from "../lib/Hex.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CMAC operation
|
||||||
|
*/
|
||||||
|
class CMAC extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CMAC constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "CMAC";
|
||||||
|
this.module = "Crypto";
|
||||||
|
this.description = "CMAC is a block-cipher based message authentication code algorithm.<br><br>RFC4493 defines AES-CMAC that uses AES encryption with a 128-bit key.<br>NIST SP 800-38B suggests usages of AES with other key lengths and Triple DES.";
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/CMAC";
|
||||||
|
this.inputType = "ArrayBuffer";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Key",
|
||||||
|
"type": "toggleString",
|
||||||
|
"value": "",
|
||||||
|
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Encryption algorithm",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["AES", "Triple DES"]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ArrayBuffer} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const key = Utils.convertToByteString(args[0].string, args[0].option);
|
||||||
|
const algo = args[1];
|
||||||
|
|
||||||
|
const info = (function() {
|
||||||
|
switch (algo) {
|
||||||
|
case "AES":
|
||||||
|
if (key.length !== 16 && key.length !== 24 && key.length !== 32) {
|
||||||
|
throw new OperationError("The key for AES must be either 16, 24, or 32 bytes (currently " + key.length + " bytes)");
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"algorithm": "AES-ECB",
|
||||||
|
"key": key,
|
||||||
|
"blockSize": 16,
|
||||||
|
"Rb": new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x87]),
|
||||||
|
};
|
||||||
|
case "Triple DES":
|
||||||
|
if (key.length !== 16 && key.length !== 24) {
|
||||||
|
throw new OperationError("The key for Triple DES must be 16 or 24 bytes (currently " + key.length + " bytes)");
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"algorithm": "3DES-ECB",
|
||||||
|
"key": key.length === 16 ? key + key.substring(0, 8) : key,
|
||||||
|
"blockSize": 8,
|
||||||
|
"Rb": new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0x1b]),
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
throw new OperationError("Undefined encryption algorithm");
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
const xor = function(a, b, out) {
|
||||||
|
if (!out) out = new Uint8Array(a.length);
|
||||||
|
for (let i = 0; i < a.length; i++) {
|
||||||
|
out[i] = a[i] ^ b[i];
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
const leftShift1 = function(a) {
|
||||||
|
const out = new Uint8Array(a.length);
|
||||||
|
let carry = 0;
|
||||||
|
for (let i = a.length - 1; i >= 0; i--) {
|
||||||
|
out[i] = (a[i] << 1) | carry;
|
||||||
|
carry = a[i] >> 7;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
const cipher = forge.cipher.createCipher(info.algorithm, info.key);
|
||||||
|
const encrypt = function(a, out) {
|
||||||
|
if (!out) out = new Uint8Array(a.length);
|
||||||
|
cipher.start();
|
||||||
|
cipher.update(forge.util.createBuffer(a));
|
||||||
|
cipher.finish();
|
||||||
|
const cipherText = cipher.output.getBytes();
|
||||||
|
for (let i = 0; i < a.length; i++) {
|
||||||
|
out[i] = cipherText.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
const L = encrypt(new Uint8Array(info.blockSize));
|
||||||
|
const K1 = leftShift1(L);
|
||||||
|
if (L[0] & 0x80) xor(K1, info.Rb, K1);
|
||||||
|
const K2 = leftShift1(K1);
|
||||||
|
if (K1[0] & 0x80) xor(K2, info.Rb, K2);
|
||||||
|
|
||||||
|
const n = Math.ceil(input.byteLength / info.blockSize);
|
||||||
|
const lastBlock = (function() {
|
||||||
|
if (n === 0) {
|
||||||
|
const data = new Uint8Array(K2);
|
||||||
|
data[0] ^= 0x80;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
const inputLast = new Uint8Array(input, info.blockSize * (n - 1));
|
||||||
|
if (inputLast.length === info.blockSize) {
|
||||||
|
return xor(inputLast, K1, inputLast);
|
||||||
|
} else {
|
||||||
|
const data = new Uint8Array(info.blockSize);
|
||||||
|
data.set(inputLast, 0);
|
||||||
|
data[inputLast.length] = 0x80;
|
||||||
|
return xor(data, K2, data);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
const X = new Uint8Array(info.blockSize);
|
||||||
|
const Y = new Uint8Array(info.blockSize);
|
||||||
|
for (let i = 0; i < n - 1; i++) {
|
||||||
|
xor(X, new Uint8Array(input, info.blockSize * i, info.blockSize), Y);
|
||||||
|
encrypt(Y, X);
|
||||||
|
}
|
||||||
|
xor(lastBlock, X, Y);
|
||||||
|
const T = encrypt(Y);
|
||||||
|
return toHexFast(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CMAC;
|
@ -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 = [];
|
||||||
|
61
src/core/operations/CaesarBoxCipher.mjs
Normal file
61
src/core/operations/CaesarBoxCipher.mjs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* @author n1073645 [n1073645@gmail.com]
|
||||||
|
* @copyright Crown Copyright 2020
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caesar Box Cipher operation
|
||||||
|
*/
|
||||||
|
class CaesarBoxCipher extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CaesarBoxCipher constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Caesar Box Cipher";
|
||||||
|
this.module = "Ciphers";
|
||||||
|
this.description = "Caesar Box is a transposition cipher used in the Roman Empire, in which letters of the message are written in rows in a square (or a rectangle) and then, read by column.";
|
||||||
|
this.infoURL = "https://www.dcode.fr/caesar-box-cipher";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
name: "Box Height",
|
||||||
|
type: "number",
|
||||||
|
value: 1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const tableHeight = args[0];
|
||||||
|
const tableWidth = Math.ceil(input.length / tableHeight);
|
||||||
|
while (input.indexOf(" ") !== -1)
|
||||||
|
input = input.replace(" ", "");
|
||||||
|
for (let i = 0; i < (tableHeight * tableWidth) - input.length; i++) {
|
||||||
|
input += "\x00";
|
||||||
|
}
|
||||||
|
let result = "";
|
||||||
|
for (let i = 0; i < tableHeight; i++) {
|
||||||
|
for (let j = i; j < input.length; j += tableHeight) {
|
||||||
|
if (input.charAt(j) !== "\x00") {
|
||||||
|
result += input.charAt(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CaesarBoxCipher;
|
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;
|
63
src/core/operations/CetaceanCipherDecode.mjs
Normal file
63
src/core/operations/CetaceanCipherDecode.mjs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* @author dolphinOnKeys [robin@weird.io]
|
||||||
|
* @copyright Crown Copyright 2022
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cetacean Cipher Decode operation
|
||||||
|
*/
|
||||||
|
class CetaceanCipherDecode extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CetaceanCipherDecode constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Cetacean Cipher Decode";
|
||||||
|
this.module = "Ciphers";
|
||||||
|
this.description = "Decode Cetacean Cipher input. <br/><br/>e.g. <code>EEEEEEEEEeeEeEEEEEEEEEEEEeeEeEEe</code> becomes <code>hi</code>";
|
||||||
|
this.infoURL = "https://hitchhikers.fandom.com/wiki/Dolphins";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
|
||||||
|
this.checks = [
|
||||||
|
{
|
||||||
|
pattern: "^(?:[eE]{16,})(?: [eE]{16,})*$",
|
||||||
|
flags: "",
|
||||||
|
args: []
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const binaryArray = [];
|
||||||
|
for (const char of input) {
|
||||||
|
if (char === " ") {
|
||||||
|
binaryArray.push(...[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]);
|
||||||
|
} else {
|
||||||
|
binaryArray.push(char === "e" ? 1 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const byteArray = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < binaryArray.length; i += 16) {
|
||||||
|
byteArray.push(binaryArray.slice(i, i + 16).join(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
return byteArray.map(byte =>
|
||||||
|
String.fromCharCode(parseInt(byte, 2))
|
||||||
|
).join("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CetaceanCipherDecode;
|
51
src/core/operations/CetaceanCipherEncode.mjs
Normal file
51
src/core/operations/CetaceanCipherEncode.mjs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* @author dolphinOnKeys [robin@weird.io]
|
||||||
|
* @copyright Crown Copyright 2022
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import {toBinary} from "../lib/Binary.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cetacean Cipher Encode operation
|
||||||
|
*/
|
||||||
|
class CetaceanCipherEncode extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CetaceanCipherEncode constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Cetacean Cipher Encode";
|
||||||
|
this.module = "Ciphers";
|
||||||
|
this.description = "Converts any input into Cetacean Cipher. <br/><br/>e.g. <code>hi</code> becomes <code>EEEEEEEEEeeEeEEEEEEEEEEEEeeEeEEe</code>";
|
||||||
|
this.infoURL = "https://hitchhikers.fandom.com/wiki/Dolphins";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const result = [];
|
||||||
|
const charArray = input.split("");
|
||||||
|
|
||||||
|
charArray.map(character => {
|
||||||
|
if (character === " ") {
|
||||||
|
result.push(character);
|
||||||
|
} else {
|
||||||
|
const binaryArray = toBinary(character.charCodeAt(0), "None", 16).split("");
|
||||||
|
result.push(binaryArray.map(str => str === "1" ? "e" : "E").join(""));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result.join("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CetaceanCipherEncode;
|
234
src/core/operations/ChaCha.mjs
Normal file
234
src/core/operations/ChaCha.mjs
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
/**
|
||||||
|
* @author joostrijneveld [joost@joostrijneveld.nl]
|
||||||
|
* @copyright Crown Copyright 2022
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
import { toHex } from "../lib/Hex.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the ChaCha block function
|
||||||
|
*
|
||||||
|
* @param {byteArray} key
|
||||||
|
* @param {byteArray} nonce
|
||||||
|
* @param {byteArray} counter
|
||||||
|
* @param {integer} rounds
|
||||||
|
* @returns {byteArray}
|
||||||
|
*/
|
||||||
|
function chacha(key, nonce, counter, rounds) {
|
||||||
|
const tau = "expand 16-byte k";
|
||||||
|
const sigma = "expand 32-byte k";
|
||||||
|
|
||||||
|
let state, c;
|
||||||
|
if (key.length === 16) {
|
||||||
|
c = Utils.strToByteArray(tau);
|
||||||
|
state = c.concat(key).concat(key);
|
||||||
|
} else {
|
||||||
|
c = Utils.strToByteArray(sigma);
|
||||||
|
state = c.concat(key);
|
||||||
|
}
|
||||||
|
state = state.concat(counter).concat(nonce);
|
||||||
|
|
||||||
|
const x = Array();
|
||||||
|
for (let i = 0; i < 64; i += 4) {
|
||||||
|
x.push(Utils.byteArrayToInt(state.slice(i, i + 4), "little"));
|
||||||
|
}
|
||||||
|
const a = [...x];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Macro to compute a 32-bit rotate-left operation
|
||||||
|
*
|
||||||
|
* @param {integer} x
|
||||||
|
* @param {integer} n
|
||||||
|
* @returns {integer}
|
||||||
|
*/
|
||||||
|
function ROL32(x, n) {
|
||||||
|
return ((x << n) & 0xFFFFFFFF) | (x >>> (32 - n));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Macro to compute a single ChaCha quarterround operation
|
||||||
|
*
|
||||||
|
* @param {integer} x
|
||||||
|
* @param {integer} a
|
||||||
|
* @param {integer} b
|
||||||
|
* @param {integer} c
|
||||||
|
* @param {integer} d
|
||||||
|
* @returns {integer}
|
||||||
|
*/
|
||||||
|
function quarterround(x, a, b, c, d) {
|
||||||
|
x[a] = ((x[a] + x[b]) & 0xFFFFFFFF); x[d] = ROL32(x[d] ^ x[a], 16);
|
||||||
|
x[c] = ((x[c] + x[d]) & 0xFFFFFFFF); x[b] = ROL32(x[b] ^ x[c], 12);
|
||||||
|
x[a] = ((x[a] + x[b]) & 0xFFFFFFFF); x[d] = ROL32(x[d] ^ x[a], 8);
|
||||||
|
x[c] = ((x[c] + x[d]) & 0xFFFFFFFF); x[b] = ROL32(x[b] ^ x[c], 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < rounds / 2; i++) {
|
||||||
|
quarterround(x, 0, 4, 8, 12);
|
||||||
|
quarterround(x, 1, 5, 9, 13);
|
||||||
|
quarterround(x, 2, 6, 10, 14);
|
||||||
|
quarterround(x, 3, 7, 11, 15);
|
||||||
|
quarterround(x, 0, 5, 10, 15);
|
||||||
|
quarterround(x, 1, 6, 11, 12);
|
||||||
|
quarterround(x, 2, 7, 8, 13);
|
||||||
|
quarterround(x, 3, 4, 9, 14);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < 16; i++) {
|
||||||
|
x[i] = (x[i] + a[i]) & 0xFFFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = Array();
|
||||||
|
for (let i = 0; i < 16; i++) {
|
||||||
|
output = output.concat(Utils.intToByteArray(x[i], 4, "little"));
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ChaCha operation
|
||||||
|
*/
|
||||||
|
class ChaCha extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ChaCha constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "ChaCha";
|
||||||
|
this.module = "Ciphers";
|
||||||
|
this.description = "ChaCha is a stream cipher designed by Daniel J. Bernstein. It is a variant of the Salsa stream cipher. Several parameterizations exist; 'ChaCha' may refer to the original construction, or to the variant as described in RFC-8439. ChaCha is often used with Poly1305, in the ChaCha20-Poly1305 AEAD construction.<br><br><b>Key:</b> ChaCha uses a key of 16 or 32 bytes (128 or 256 bits).<br><br><b>Nonce:</b> ChaCha uses a nonce of 8 or 12 bytes (64 or 96 bits).<br><br><b>Counter:</b> ChaCha uses a counter of 4 or 8 bytes (32 or 64 bits); together, the nonce and counter must add up to 16 bytes. The counter starts at zero at the start of the keystream, and is incremented at every 64 bytes.";
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/Salsa20#ChaCha_variant";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Key",
|
||||||
|
"type": "toggleString",
|
||||||
|
"value": "",
|
||||||
|
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Nonce",
|
||||||
|
"type": "toggleString",
|
||||||
|
"value": "",
|
||||||
|
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64", "Integer"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Counter",
|
||||||
|
"type": "number",
|
||||||
|
"value": 0,
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Rounds",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["20", "12", "8"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Input",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["Hex", "Raw"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Output",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["Raw", "Hex"]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const key = Utils.convertToByteArray(args[0].string, args[0].option),
|
||||||
|
nonceType = args[1].option,
|
||||||
|
rounds = parseInt(args[3], 10),
|
||||||
|
inputType = args[4],
|
||||||
|
outputType = args[5];
|
||||||
|
|
||||||
|
if (key.length !== 16 && key.length !== 32) {
|
||||||
|
throw new OperationError(`Invalid key length: ${key.length} bytes.
|
||||||
|
|
||||||
|
ChaCha uses a key of 16 or 32 bytes (128 or 256 bits).`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let counter, nonce, counterLength;
|
||||||
|
if (nonceType === "Integer") {
|
||||||
|
nonce = Utils.intToByteArray(parseInt(args[1].string, 10), 12, "little");
|
||||||
|
counterLength = 4;
|
||||||
|
} else {
|
||||||
|
nonce = Utils.convertToByteArray(args[1].string, args[1].option);
|
||||||
|
if (!(nonce.length === 12 || nonce.length === 8)) {
|
||||||
|
throw new OperationError(`Invalid nonce length: ${nonce.length} bytes.
|
||||||
|
|
||||||
|
ChaCha uses a nonce of 8 or 12 bytes (64 or 96 bits).`);
|
||||||
|
}
|
||||||
|
counterLength = 16 - nonce.length;
|
||||||
|
}
|
||||||
|
counter = Utils.intToByteArray(args[2], counterLength, "little");
|
||||||
|
|
||||||
|
const output = [];
|
||||||
|
input = Utils.convertToByteArray(input, inputType);
|
||||||
|
|
||||||
|
let counterAsInt = Utils.byteArrayToInt(counter, "little");
|
||||||
|
for (let i = 0; i < input.length; i += 64) {
|
||||||
|
counter = Utils.intToByteArray(counterAsInt, counterLength, "little");
|
||||||
|
const stream = chacha(key, nonce, counter, rounds);
|
||||||
|
for (let j = 0; j < 64 && i + j < input.length; j++) {
|
||||||
|
output.push(input[i + j] ^ stream[j]);
|
||||||
|
}
|
||||||
|
counterAsInt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outputType === "Hex") {
|
||||||
|
return toHex(output);
|
||||||
|
} else {
|
||||||
|
return Utils.arrayBufferToStr(Uint8Array.from(output).buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highlight ChaCha
|
||||||
|
*
|
||||||
|
* @param {Object[]} pos
|
||||||
|
* @param {number} pos[].start
|
||||||
|
* @param {number} pos[].end
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {Object[]} pos
|
||||||
|
*/
|
||||||
|
highlight(pos, args) {
|
||||||
|
const inputType = args[4],
|
||||||
|
outputType = args[5];
|
||||||
|
if (inputType === "Raw" && outputType === "Raw") {
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highlight ChaCha in reverse
|
||||||
|
*
|
||||||
|
* @param {Object[]} pos
|
||||||
|
* @param {number} pos[].start
|
||||||
|
* @param {number} pos[].end
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {Object[]} pos
|
||||||
|
*/
|
||||||
|
highlightReverse(pos, args) {
|
||||||
|
const inputType = args[4],
|
||||||
|
outputType = args[5];
|
||||||
|
if (inputType === "Raw" && outputType === "Raw") {
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChaCha;
|
@ -1,6 +1,9 @@
|
|||||||
/**
|
/**
|
||||||
* Emulation of Colossus.
|
* Emulation of Colossus.
|
||||||
*
|
*
|
||||||
|
* Tested against the Colossus Rebuild at Bletchley Park's TNMOC
|
||||||
|
* using a variety of inputs and settings to confirm correctness.
|
||||||
|
*
|
||||||
* @author VirtualColossus [martin@virtualcolossus.co.uk]
|
* @author VirtualColossus [martin@virtualcolossus.co.uk]
|
||||||
* @copyright Crown Copyright 2019
|
* @copyright Crown Copyright 2019
|
||||||
* @license Apache-2.0
|
* @license Apache-2.0
|
||||||
|
@ -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 = [
|
||||||
|
@ -64,6 +64,7 @@ class ConditionalJump extends Operation {
|
|||||||
jmpIndex = getLabelIndex(label, state);
|
jmpIndex = getLabelIndex(label, state);
|
||||||
|
|
||||||
if (state.numJumps >= maxJumps || jmpIndex === -1) {
|
if (state.numJumps >= maxJumps || jmpIndex === -1) {
|
||||||
|
state.numJumps = 0;
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,6 +74,8 @@ class ConditionalJump extends Operation {
|
|||||||
if (!invert && strMatch || invert && !strMatch) {
|
if (!invert && strMatch || invert && !strMatch) {
|
||||||
state.progress = jmpIndex;
|
state.progress = jmpIndex;
|
||||||
state.numJumps++;
|
state.numJumps++;
|
||||||
|
} else {
|
||||||
|
state.numJumps = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,8 +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 jimplib from "jimp/es/index.js";
|
import Jimp from "jimp/es/index.js";
|
||||||
const jimp = jimplib.default ? jimplib.default : jimplib;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contain Image operation
|
* Contain Image operation
|
||||||
@ -92,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)) {
|
||||||
@ -114,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})`);
|
||||||
}
|
}
|
||||||
@ -124,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,8 +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 jimplib from "jimp/es/index.js";
|
import Jimp from "jimp/es/index.js";
|
||||||
const jimp = jimplib.default ? jimplib.default : jimplib;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert Image Format operation
|
* Convert Image Format operation
|
||||||
@ -77,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];
|
||||||
@ -99,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})`);
|
||||||
}
|
}
|
||||||
|
@ -9,8 +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 jimplib from "jimp/es/index.js";
|
import jimp from "jimp/es/index.js";
|
||||||
const jimp = jimplib.default ? jimplib.default : jimplib;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cover Image operation
|
* Cover Image operation
|
||||||
|
@ -9,8 +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 jimplib from "jimp/es/index.js";
|
import Jimp from "jimp/es/index.js";
|
||||||
const jimp = jimplib.default ? jimplib.default : jimplib;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Crop Image operation
|
* Crop Image operation
|
||||||
@ -100,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})`);
|
||||||
}
|
}
|
||||||
@ -120,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.";
|
this.description = "DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.<br><br><b>Key:</b> DES uses a key length of 8 bytes (64 bits).<br>Triple DES uses a key length of 24 bytes (192 bits).<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used as a default.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Data_Encryption_Standard";
|
this.infoURL = "https://wikipedia.org/wiki/Data_Encryption_Standard";
|
||||||
this.inputType = "string";
|
this.inputType = "string";
|
||||||
this.outputType = "string";
|
this.outputType = "string";
|
||||||
@ -42,7 +42,7 @@ class DESDecrypt extends Operation {
|
|||||||
{
|
{
|
||||||
"name": "Mode",
|
"name": "Mode",
|
||||||
"type": "option",
|
"type": "option",
|
||||||
"value": ["CBC", "CFB", "OFB", "CTR", "ECB"]
|
"value": ["CBC", "CFB", "OFB", "CTR", "ECB", "CBC/NoPadding", "ECB/NoPadding"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Input",
|
"name": "Input",
|
||||||
@ -65,7 +65,9 @@ class DESDecrypt extends Operation {
|
|||||||
run(input, args) {
|
run(input, args) {
|
||||||
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
||||||
iv = Utils.convertToByteArray(args[1].string, args[1].option),
|
iv = Utils.convertToByteArray(args[1].string, args[1].option),
|
||||||
[,, mode, inputType, outputType] = args;
|
mode = args[2].substring(0, 3),
|
||||||
|
noPadding = args[2].endsWith("NoPadding"),
|
||||||
|
[,,, inputType, outputType] = args;
|
||||||
|
|
||||||
if (key.length !== 8) {
|
if (key.length !== 8) {
|
||||||
throw new OperationError(`Invalid key length: ${key.length} bytes
|
throw new OperationError(`Invalid key length: ${key.length} bytes
|
||||||
@ -83,6 +85,14 @@ Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
|
|||||||
input = Utils.convertToByteString(input, inputType);
|
input = Utils.convertToByteString(input, inputType);
|
||||||
|
|
||||||
const decipher = forge.cipher.createDecipher("DES-" + mode, key);
|
const decipher = forge.cipher.createDecipher("DES-" + mode, key);
|
||||||
|
|
||||||
|
/* Allow for a "no padding" mode */
|
||||||
|
if (noPadding) {
|
||||||
|
decipher.mode.unpad = function(output, options) {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
decipher.start({iv: iv});
|
decipher.start({iv: iv});
|
||||||
decipher.update(forge.util.createBuffer(input));
|
decipher.update(forge.util.createBuffer(input));
|
||||||
const result = decipher.finish();
|
const result = decipher.finish();
|
||||||
|
@ -51,10 +51,27 @@ class DNSOverHTTPS extends Operation {
|
|||||||
value: [
|
value: [
|
||||||
"A",
|
"A",
|
||||||
"AAAA",
|
"AAAA",
|
||||||
"TXT",
|
"ANAME",
|
||||||
"MX",
|
"CERT",
|
||||||
|
"CNAME",
|
||||||
"DNSKEY",
|
"DNSKEY",
|
||||||
"NS"
|
"HTTPS",
|
||||||
|
"IPSECKEY",
|
||||||
|
"LOC",
|
||||||
|
"MX",
|
||||||
|
"NS",
|
||||||
|
"OPENPGPKEY",
|
||||||
|
"PTR",
|
||||||
|
"RRSIG",
|
||||||
|
"SIG",
|
||||||
|
"SOA",
|
||||||
|
"SPF",
|
||||||
|
"SRV",
|
||||||
|
"SSHFP",
|
||||||
|
"TA",
|
||||||
|
"TXT",
|
||||||
|
"URI",
|
||||||
|
"ANY"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
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;
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
import Operation from "../Operation.mjs";
|
import Operation from "../Operation.mjs";
|
||||||
import cptable from "codepage";
|
import cptable from "codepage";
|
||||||
import {IO_FORMAT} from "../lib/ChrEnc.mjs";
|
import {CHR_ENC_CODE_PAGES} from "../lib/ChrEnc.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode text operation
|
* Decode text operation
|
||||||
@ -26,7 +26,7 @@ class DecodeText extends Operation {
|
|||||||
"<br><br>",
|
"<br><br>",
|
||||||
"Supported charsets are:",
|
"Supported charsets are:",
|
||||||
"<ul>",
|
"<ul>",
|
||||||
Object.keys(IO_FORMAT).map(e => `<li>${e}</li>`).join("\n"),
|
Object.keys(CHR_ENC_CODE_PAGES).map(e => `<li>${e}</li>`).join("\n"),
|
||||||
"</ul>",
|
"</ul>",
|
||||||
].join("\n");
|
].join("\n");
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Character_encoding";
|
this.infoURL = "https://wikipedia.org/wiki/Character_encoding";
|
||||||
@ -36,7 +36,7 @@ class DecodeText extends Operation {
|
|||||||
{
|
{
|
||||||
"name": "Encoding",
|
"name": "Encoding",
|
||||||
"type": "option",
|
"type": "option",
|
||||||
"value": Object.keys(IO_FORMAT)
|
"value": Object.keys(CHR_ENC_CODE_PAGES)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -47,7 +47,7 @@ class DecodeText extends Operation {
|
|||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const format = IO_FORMAT[args[0]];
|
const format = CHR_ENC_CODE_PAGES[args[0]];
|
||||||
return cptable.utils.decode(format, new Uint8Array(input));
|
return cptable.utils.decode(format, new Uint8Array(input));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,12 +62,14 @@ 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(
|
||||||
key = CryptoJS.EvpKDF(passphrase, salt, {
|
Utils.convertToByteString(args[4].string, args[4].option)),
|
||||||
|
key = CryptoJS.EvpKDF(passphrase, salt, { // lgtm [js/insufficient-password-hash]
|
||||||
keySize: keySize,
|
keySize: keySize,
|
||||||
hasher: CryptoJS.algo[hasher],
|
hasher: CryptoJS.algo[hasher],
|
||||||
iterations: iterations,
|
iterations: iterations,
|
||||||
|
138
src/core/operations/DeriveHKDFKey.mjs
Normal file
138
src/core/operations/DeriveHKDFKey.mjs
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
/**
|
||||||
|
* @author mikecat
|
||||||
|
* @copyright Crown Copyright 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import CryptoApi from "crypto-api/src/crypto-api.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derive HKDF Key operation
|
||||||
|
*/
|
||||||
|
class DeriveHKDFKey extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DeriveHKDFKey constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Derive HKDF key";
|
||||||
|
this.module = "Crypto";
|
||||||
|
this.description = "A simple Hashed Message Authenticaton Code (HMAC)-based key derivation function (HKDF), defined in RFC5869.";
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/HKDF";
|
||||||
|
this.inputType = "ArrayBuffer";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Salt",
|
||||||
|
"type": "toggleString",
|
||||||
|
"value": "",
|
||||||
|
"toggleValues": ["Hex", "Decimal", "Base64", "UTF8", "Latin1"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Info",
|
||||||
|
"type": "toggleString",
|
||||||
|
"value": "",
|
||||||
|
"toggleValues": ["Hex", "Decimal", "Base64", "UTF8", "Latin1"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Hashing function",
|
||||||
|
"type": "option",
|
||||||
|
"value": [
|
||||||
|
"MD2",
|
||||||
|
"MD4",
|
||||||
|
"MD5",
|
||||||
|
"SHA0",
|
||||||
|
"SHA1",
|
||||||
|
"SHA224",
|
||||||
|
"SHA256",
|
||||||
|
"SHA384",
|
||||||
|
"SHA512",
|
||||||
|
"SHA512/224",
|
||||||
|
"SHA512/256",
|
||||||
|
"RIPEMD128",
|
||||||
|
"RIPEMD160",
|
||||||
|
"RIPEMD256",
|
||||||
|
"RIPEMD320",
|
||||||
|
"HAS160",
|
||||||
|
"Whirlpool",
|
||||||
|
"Whirlpool-0",
|
||||||
|
"Whirlpool-T",
|
||||||
|
"Snefru"
|
||||||
|
],
|
||||||
|
"defaultIndex": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Extract mode",
|
||||||
|
"type": "argSelector",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"name": "with salt",
|
||||||
|
"on": [0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "no salt",
|
||||||
|
"off": [0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "skip",
|
||||||
|
"off": [0]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "L (number of output octets)",
|
||||||
|
"type": "number",
|
||||||
|
"value": 16,
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ArrayBuffer} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {ArrayBuffer}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const argSalt = Utils.convertToByteString(args[0].string || "", args[0].option),
|
||||||
|
info = Utils.convertToByteString(args[1].string || "", args[1].option),
|
||||||
|
hashFunc = args[2].toLowerCase(),
|
||||||
|
extractMode = args[3],
|
||||||
|
L = args[4],
|
||||||
|
IKM = Utils.arrayBufferToStr(input, false),
|
||||||
|
hasher = CryptoApi.getHasher(hashFunc),
|
||||||
|
HashLen = hasher.finalize().length;
|
||||||
|
|
||||||
|
if (L < 0) {
|
||||||
|
throw new OperationError("L must be non-negative");
|
||||||
|
}
|
||||||
|
if (L > 255 * HashLen) {
|
||||||
|
throw new OperationError("L too large (maximum length for " + args[2] + " is " + (255 * HashLen) + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
const hmacHash = function(key, data) {
|
||||||
|
hasher.reset();
|
||||||
|
const mac = CryptoApi.getHmac(key, hasher);
|
||||||
|
mac.update(data);
|
||||||
|
return mac.finalize();
|
||||||
|
};
|
||||||
|
const salt = extractMode === "with salt" ? argSalt : "\0".repeat(HashLen);
|
||||||
|
const PRK = extractMode === "skip" ? IKM : hmacHash(salt, IKM);
|
||||||
|
let T = "";
|
||||||
|
let result = "";
|
||||||
|
for (let i = 1; i <= 255 && result.length < L; i++) {
|
||||||
|
const TNext = hmacHash(PRK, T + info + String.fromCharCode(i));
|
||||||
|
result += TNext;
|
||||||
|
T = TNext;
|
||||||
|
}
|
||||||
|
return CryptoApi.encoder.toHex(result.substring(0, L));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DeriveHKDFKey;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user