Merge branch 'master' into new
This commit is contained in:
commit
b47b13d987
62
.github/workflows/cla-close-stale.yml
vendored
Normal file
62
.github/workflows/cla-close-stale.yml
vendored
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
name: Close Stale Unsigned CLA PRs
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# Runs daily at 01:30 UTC.
|
||||||
|
- cron: '30 1 * * *'
|
||||||
|
workflow_dispatch: {}
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
# Configurable intervals (days).
|
||||||
|
# DAYS_BEFORE_WARNING = grace period before the warning comment.
|
||||||
|
# DAYS_BEFORE_CLOSURE = further period after the warning before closing.
|
||||||
|
env:
|
||||||
|
DAYS_BEFORE_WARNING: 7
|
||||||
|
DAYS_BEFORE_CLOSURE: 21
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Close stale unsigned-CLA PRs
|
||||||
|
uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 #v10.3.0
|
||||||
|
with:
|
||||||
|
# ---- Guards: only act on PRs carrying the CLA label ----
|
||||||
|
only-labels: 'awaiting cla'
|
||||||
|
|
||||||
|
# Never touch issues — PRs only.
|
||||||
|
days-before-issue-stale: -1
|
||||||
|
days-before-issue-close: -1
|
||||||
|
|
||||||
|
# ---- Timing ----
|
||||||
|
# DAYS_BEFORE_WARNING: days of inactivity before the warning comment.
|
||||||
|
days-before-pr-stale: ${{ env.DAYS_BEFORE_WARNING }}
|
||||||
|
# DAYS_BEFORE_CLOSURE: days after being marked stale before closing.
|
||||||
|
days-before-pr-close: ${{ env.DAYS_BEFORE_CLOSURE }}
|
||||||
|
|
||||||
|
# ---- Warning comment (posted once when marked stale) ----
|
||||||
|
stale-pr-message: >
|
||||||
|
As we are unable to accept contributions unless the CLA has
|
||||||
|
been signed, this PR will be automatically closed if the CLA
|
||||||
|
is not signed within ${{ env.DAYS_BEFORE_CLOSURE }} days.
|
||||||
|
|
||||||
|
# ---- Close comment ----
|
||||||
|
close-pr-message: >
|
||||||
|
This PR has been automatically closed as the CLA remains
|
||||||
|
unsigned. We will be happy to have it reopened if the CLA
|
||||||
|
is signed subsequently.
|
||||||
|
|
||||||
|
# A dedicated marker label so we can track stale state without
|
||||||
|
# interfering with the "awaiting cla" label.
|
||||||
|
stale-pr-label: 'cla-stale'
|
||||||
|
|
||||||
|
# If the PR is updated after being marked stale, remove the marker
|
||||||
|
# so the warning-then-close cycle restarts cleanly.
|
||||||
|
remove-pr-stale-when-updated: true
|
||||||
|
|
||||||
|
# Process enough PRs per run for busy repos.
|
||||||
|
operations-per-run: 200
|
||||||
87
.github/workflows/cla-label.yml
vendored
Normal file
87
.github/workflows/cla-label.yml
vendored
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
name: CLA Label Sync
|
||||||
|
|
||||||
|
on:
|
||||||
|
issue_comment:
|
||||||
|
types: [created, edited]
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened, synchronize, reopened]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
issues: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
sync-label:
|
||||||
|
# Only run for PRs (issue_comment fires for issues too)
|
||||||
|
if: >-
|
||||||
|
github.event_name == 'pull_request_target' ||
|
||||||
|
(github.event.issue.pull_request != null)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Sync "awaiting cla" label
|
||||||
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #v9.0.0
|
||||||
|
env:
|
||||||
|
AWAITING_LABEL: 'awaiting cla'
|
||||||
|
# Bot login that posts the CLA comment. Common values:
|
||||||
|
# 'github-actions[bot]', 'CLAassistant', 'cla-assistant[bot]'
|
||||||
|
CLA_BOT_LOGINS: 'CLAassistant'
|
||||||
|
# Regex (case-insensitive) that matches an UNSIGNED CLA comment
|
||||||
|
NOT_SIGNED_REGEX: 'cla-assistant.io/pull/badge/not_signed'
|
||||||
|
# Regex (case-insensitive) that matches a SIGNED CLA comment
|
||||||
|
SIGNED_REGEX: 'cla-assistant.io/pull/badge/signed'
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const awaitingLabel = process.env.AWAITING_LABEL;
|
||||||
|
const botLogins = process.env.CLA_BOT_LOGINS.split(',').map(s => s.trim().toLowerCase());
|
||||||
|
const notSigned = new RegExp(process.env.NOT_SIGNED_REGEX, 'i');
|
||||||
|
const signed = new RegExp(process.env.SIGNED_REGEX, 'i');
|
||||||
|
|
||||||
|
// Resolve PR number for either trigger
|
||||||
|
const prNumber = context.eventName === 'pull_request_target'
|
||||||
|
? context.payload.pull_request.number
|
||||||
|
: context.payload.issue.number;
|
||||||
|
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
|
||||||
|
// Pull the full comment history to find the latest CLA bot comment
|
||||||
|
const comments = await github.paginate(github.rest.issues.listComments, {
|
||||||
|
owner, repo, issue_number: prNumber, per_page: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
const claComments = comments.filter(c =>
|
||||||
|
botLogins.includes((c.user?.login || '').toLowerCase()) &&
|
||||||
|
(notSigned.test(c.body) || signed.test(c.body))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (claComments.length === 0) {
|
||||||
|
core.info('No CLA Assistant comment found yet; nothing to do.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const latest = claComments[claComments.length - 1];
|
||||||
|
const isSigned = signed.test(latest.body) && !notSigned.test(latest.body);
|
||||||
|
|
||||||
|
core.info(`Latest CLA comment (id ${latest.id}) => signed=${isSigned}`);
|
||||||
|
|
||||||
|
// Current labels
|
||||||
|
const { data: issue } = await github.rest.issues.get({
|
||||||
|
owner, repo, issue_number: prNumber,
|
||||||
|
});
|
||||||
|
const hasLabel = issue.labels.some(l =>
|
||||||
|
(typeof l === 'string' ? l : l.name) === awaitingLabel
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isSigned && hasLabel) {
|
||||||
|
await github.rest.issues.removeLabel({
|
||||||
|
owner, repo, issue_number: prNumber, name: awaitingLabel,
|
||||||
|
}).catch(e => core.warning(`removeLabel failed: ${e.message}`));
|
||||||
|
core.info(`Removed "${awaitingLabel}".`);
|
||||||
|
} else if (!isSigned && !hasLabel) {
|
||||||
|
await github.rest.issues.addLabels({
|
||||||
|
owner, repo, issue_number: prNumber, labels: [awaitingLabel],
|
||||||
|
});
|
||||||
|
core.info(`Added "${awaitingLabel}".`);
|
||||||
|
} else {
|
||||||
|
core.info('Label already in the correct state.');
|
||||||
|
}
|
||||||
2
.github/workflows/master.yml
vendored
2
.github/workflows/master.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
|||||||
pages: write
|
pages: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
|
|
||||||
- name: Set node version
|
- name: Set node version
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
|
|||||||
4
.github/workflows/pull_requests.yml
vendored
4
.github/workflows/pull_requests.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
|||||||
main:
|
main:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
|
|
||||||
- name: Set node version
|
- name: Set node version
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
@ -64,7 +64,7 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
|
||||||
|
|
||||||
- name: Production Image Build
|
- name: Production Image Build
|
||||||
if: success()
|
if: success()
|
||||||
|
|||||||
6
.github/workflows/releases.yml
vendored
6
.github/workflows/releases.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
|
|
||||||
- name: Set node version
|
- name: Set node version
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
@ -64,7 +64,7 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
|
||||||
|
|
||||||
- name: Image Metadata
|
- name: Image Metadata
|
||||||
id: image-metadata
|
id: image-metadata
|
||||||
@ -110,7 +110,7 @@ jobs:
|
|||||||
needs: main
|
needs: main
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
|
|
||||||
- name: Set node version
|
- name: Set node version
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,6 +10,7 @@ src/core/config/OperationConfig.json
|
|||||||
src/core/operations/index.mjs
|
src/core/operations/index.mjs
|
||||||
src/node/config/OperationConfig.json
|
src/node/config/OperationConfig.json
|
||||||
src/node/index.mjs
|
src/node/index.mjs
|
||||||
|
tests/operations/index.mjs
|
||||||
**/*.DS_Store
|
**/*.DS_Store
|
||||||
tests/browser/output/*
|
tests/browser/output/*
|
||||||
.node-version
|
.node-version
|
||||||
|
|||||||
75
AGENTS.md
Normal file
75
AGENTS.md
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# CyberChef Agent Development Guide
|
||||||
|
|
||||||
|
## Project
|
||||||
|
|
||||||
|
CyberChef is a client-side web app and Node.js package for encoding, decoding, encryption, compression, parsing, and data analysis operations. Users build recipes from operations and run them against browser-local input.
|
||||||
|
|
||||||
|
Core principles for changes:
|
||||||
|
|
||||||
|
- Keep operations and features client-side, avoiding external services whenever possible. CyberChef is used on airgapped networks.
|
||||||
|
- Keep latency low. Keep large libraries in separate modules so they are downloaded only by users who invoke the relevant operations.
|
||||||
|
- Prefer Vanilla JS over jQuery or other frameworks.
|
||||||
|
- Avoid new external package dependencies unless absolutely necessary. Reuse platform APIs and existing project utilities first.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
CyberChef expects Node.js `>=24 <25`.
|
||||||
|
|
||||||
|
- Install: `npm install`
|
||||||
|
- Development server: `npm start`
|
||||||
|
- Production build: `npm run build`
|
||||||
|
- Build Node package artifacts: `npm run node`
|
||||||
|
- Lint: `npm run lint`
|
||||||
|
- Spell/grammar lint for `src`: `npm run lint:grammar`
|
||||||
|
- Full non-UI test suite: `npm test`
|
||||||
|
- UI tests: `npm run testui`
|
||||||
|
- UI tests against the dev server: `npm run testuidev`
|
||||||
|
- Node REPL: `npm run repl`
|
||||||
|
|
||||||
|
## New operations
|
||||||
|
|
||||||
|
Use the existing generator for new operations:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run newop
|
||||||
|
```
|
||||||
|
|
||||||
|
This wraps `node src/core/config/scripts/newOperation.mjs`. Run it from the repository root. Afterwards:
|
||||||
|
|
||||||
|
- Implement the operation in `src/core/operations/<Operation>.mjs`.
|
||||||
|
- Add or verify its category entry in `src/core/config/Categories.json`.
|
||||||
|
- Implement the tests in `tests/operations/tests/<Operation>.mjs`.
|
||||||
|
|
||||||
|
## Coding conventions
|
||||||
|
|
||||||
|
* Indentation: Each block should consist of 4 spaces
|
||||||
|
* Object/namespace identifiers: CamelCase
|
||||||
|
* Function/variable names: camelCase
|
||||||
|
* Constants: UNDERSCORE_UPPER_CASE
|
||||||
|
* Source code encoding: UTF-8 (without BOM)
|
||||||
|
* All source files must end with a newline
|
||||||
|
* Line endings: UNIX style (\n)
|
||||||
|
|
||||||
|
## Operation argument validation and errors
|
||||||
|
|
||||||
|
Use the existing operation recipe validation framework. Declare constraints in operation `ingList` entries via operation `args` so `Ingredient` and `Operation.validateIngredients()` can enforce them before `run()` executes. Do not use checks inside `run()` when a constraint belongs in argument metadata.
|
||||||
|
|
||||||
|
For invalid arguments and failures caused by user input, throw `OperationError` from `src/core/errors/OperationError.mjs`. Recipe execution treats `OperationError` as expected output, and is not appropriate for programmer bugs or unexpected internal exceptions.
|
||||||
|
|
||||||
|
## Testing instructions
|
||||||
|
|
||||||
|
Bug fixes require regression tests that must fail before the fix and pass after it. New features require tests including edge cases and invalid input. Use both NodeJS tests and UI tests when a change affects both the operation/API behaviour and browser interaction.
|
||||||
|
|
||||||
|
- Operation tests live under `tests/operations/tests/` and are run by `npm test` through `tests/operations/index.mjs`.
|
||||||
|
- Node API tests live under `tests/node/tests/` and are run by `npm test` through `tests/node/index.mjs`.
|
||||||
|
- UI tests live under `tests/browser/` and are run with `npm run testui` or `npm run testuidev`.
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
- Avoid prototype pollution. Utilise `Object.create(null)`, `Map`, and `Object.prototype.hasOwnProperty.call(...)`.
|
||||||
|
- Avoid XSS. Escape strings before rendering and prefer text APIs over `innerHTML`.
|
||||||
|
- Avoid resource exhaustion. Bound input sizes, counts, recursion depth and loop iterations.
|
||||||
|
|
||||||
|
## Issue and pull request policy
|
||||||
|
|
||||||
|
Do not open GitHub issues or pull requests on behalf of users. If a user asks to create an issue or pull request, stage a root-level file named `AI_MARKER`.
|
||||||
171
CHANGELOG.md
171
CHANGELOG.md
@ -13,6 +13,86 @@ All major and minor version changes will be documented in this file. Details of
|
|||||||
|
|
||||||
## Details
|
## Details
|
||||||
|
|
||||||
|
### [11.2.0] - 2026-06-17
|
||||||
|
This release includes a security fix ([#2569])
|
||||||
|
- Security: Chart operation prototype protection [@C85297] | [#2569]
|
||||||
|
- Update website references [@C85297] | [#2566]
|
||||||
|
- Fix: Add input validation for XOR Checksum blocksize (#2537) [@dweep-js] | [#2542]
|
||||||
|
- Fix: Reverse highlights unwind incorrectly [@kendallgoto] [@C85297] | [#2022]
|
||||||
|
- Fix Uint8Array concat crash in Parse IPv4 header [@Zish19] | [#2409]
|
||||||
|
- Fix typos and documentation errors (bytes→bits, wrong release link, spelling) [@qa2me] [@GCHQDeveloper581] | [#2404]
|
||||||
|
- Add integer check for alphabet size [@heapframe] [@GCHQDeveloper581] | [#2458]
|
||||||
|
- fix: validate hexdump width upper bound [@skyswordw] | [#2514]
|
||||||
|
|
||||||
|
### [11.1.0] - 2026-06-13
|
||||||
|
This release includes a security fix ([#2557])
|
||||||
|
- Security: Add fix, and tests, for Lorem Ipsum DoS issue [@GCHQDeveloper581] | [#2557]
|
||||||
|
- chore (deps): bump the patch-updates group with 4 updates | [#2552]
|
||||||
|
- chore (deps): bump the actions-dependencies group with 2 updates | [#2551]
|
||||||
|
- chore (deps): bump the docker-dependencies group with 2 updates | [#2550]
|
||||||
|
- chore (deps): bump protobufjs from 8.5.0 to 8.6.2 in the minor-updates group | [#2553]
|
||||||
|
- Security Policy Update [@C85297] | [#2547]
|
||||||
|
- Fix spurious error messages generated during webpack build [@GCHQDeveloper581] | [#2545]
|
||||||
|
- chore (deps): bump shell-quote from 1.8.3 to 1.8.4 | [#2543]
|
||||||
|
- Implementing ROR13 feature [@Fufu-btw] | [#2539]
|
||||||
|
- New operation improvements [@jl5193] [@GCHQDeveloper581] | [#1431]
|
||||||
|
- Npm and yarn/major version updates [@GCHQDeveloper581] | [#2527]
|
||||||
|
- Update README to reflect AES Decrypt changes [@andreasrtv] | [#2502]
|
||||||
|
- feat: add Escape Smart Characters operation [@HarelKatz] | [#2391]
|
||||||
|
- feat: Get AES IV from input (QoL) [@andreasrtv] | [#2471]
|
||||||
|
- fix: validate text encoding options [@SyedIshmumAhnaf] | [#2497]
|
||||||
|
- chore (deps): bump the minor-updates group with 5 updates [@GCHQDeveloper581] | [#2500]
|
||||||
|
- chore (deps): bump the patch-updates group with 2 updates | [#2499]
|
||||||
|
- chore (deps): bump nginxinc/nginx-unprivileged from `df0e9ed` to `0a1e718` in the docker-dependencies group | [#2498]
|
||||||
|
- Add remove ANSI escape codes operation [@Louis-Ladd] [@GCHQDeveloper581] | [#2143]
|
||||||
|
- Fix option ingredients being overwriten [@C85297] | [#2341]
|
||||||
|
- chore (deps): bump qs and express | [#2478]
|
||||||
|
- chore (deps): bump tmp from 0.2.5 to 0.2.7 | [#2479]
|
||||||
|
- chore (deps): bump the patch-updates group across 1 directory with 6 updates | [#2463]
|
||||||
|
- chore (deps): bump the docker-dependencies group across 1 directory with 2 updates | [#2468]
|
||||||
|
- chore (deps): bump terser from 5.46.2 to 5.48.0 | [#2385]
|
||||||
|
- Make dependabot quieter [@GCHQDeveloper581] | [#2467]
|
||||||
|
- update sitemap [@Blank0120] | [#2443]
|
||||||
|
- Bump webpack-dev-server to 5.2.4 [@GCHQDeveloper581] | [#2417]
|
||||||
|
- Fix pgp tests [@GCHQDeveloper581] [@C85297] | [#2461]
|
||||||
|
- chore (deps): bump the patch-updates group across 1 directory with 4 updates | [#2438]
|
||||||
|
- chore (deps): bump docker/setup-buildx-action from 4.0.0 to 4.1.0 | [#2439]
|
||||||
|
- chore (deps): bump docker/login-action from 4.1.0 to 4.2.0 | [#2441]
|
||||||
|
- chore (deps): bump docker/metadata-action from 6.0.0 to 6.1.0 | [#2442]
|
||||||
|
- update bson [@Blank0120] [@GCHQDeveloper581] | [#2425]
|
||||||
|
- chore (deps): bump webpack from 5.106.2 to 5.107.1 | [#2428]
|
||||||
|
- chore (deps): bump protobufjs from 7.5.8 to 7.6.0 | [#2429]
|
||||||
|
- chore (deps): bump sql-formatter from 15.7.4 to 15.8.0 | [#2430]
|
||||||
|
- chore (deps): bump docker/build-push-action from 7.1.0 to 7.2.0 | [#2431]
|
||||||
|
- Fix flaky `npm run testui` [@lzandman] | [#2412]
|
||||||
|
- Include git ref in website download zip name [@C85297] | [#2339]
|
||||||
|
- Bump nginxinc/nginx-unprivileged from `808f784` to `b9f7ba1` | [#2389]
|
||||||
|
- Series Chart HTML Formatting fix [@C85297] | [#2403]
|
||||||
|
- Parse Ethernet Frame HTML formatting fix [@C85297] | [#2402]
|
||||||
|
- Parse IPv4 Header HTML formatting fix [@C85297] | [#2401]
|
||||||
|
- Update chromedriver, and install corresponding chrome in workflows (fixes build) [@GCHQDeveloper581] | [#2387]
|
||||||
|
- chore (deps): bump @codemirror/view from 6.41.1 to 6.43.0 | [#2384]
|
||||||
|
- chore (deps): bump globals from 17.5.0 to 17.6.0 | [#2386]
|
||||||
|
- chore (deps): bump the patch-updates group across 1 directory with 3 updates | [#2388]
|
||||||
|
- [StepSecurity] Apply security best practices [@GCHQDeveloper581] StepSecurity Bot <bot@stepsecurity.io> | [#2378]
|
||||||
|
- Build docker container for arm v7 as well [@GCHQDeveloper581] | [#2379]
|
||||||
|
- chore (deps): bump fast-uri from 3.1.0 to 3.1.2 | [#2372]
|
||||||
|
- update bcryptjs [@C85297] [@GCHQDeveloper581] | [#2368]
|
||||||
|
- chore (deps): bump picomatch from 2.3.1 to 2.3.2 | [#2370]
|
||||||
|
- chore (deps): bump ip-address from 10.1.0 to 10.2.0 | [#2371]
|
||||||
|
- chore (deps): bump axios from 1.15.0 to 1.16.0 | [#2369]
|
||||||
|
- feat(operation-wrap): add new Wrap operation to format text at specified line width [@0xff1ce] | [#1882]
|
||||||
|
- chore (deps): bump the patch-updates group across 1 directory with 5 updates | [#2354]
|
||||||
|
- chore (deps): bump docker/login-action from 3 to 4 | [#2363]
|
||||||
|
- chore (deps): bump docker/setup-buildx-action from 3 to 4 | [#2364]
|
||||||
|
- chore (deps): bump crazy-max/ghaction-github-pages from 3 to 5 | [#2365]
|
||||||
|
- chore (deps): bump docker/metadata-action from 4 to 6 | [#2366]
|
||||||
|
- chore (deps): bump docker/setup-qemu-action from 3 to 4 | [#2367]
|
||||||
|
- Update dependabot for Node 24. [@GCHQDeveloper581] | [#2361]
|
||||||
|
- chore (deps): bump uuid from 13.0.0 to 14.0.0 | [#2332]
|
||||||
|
- chore (deps): bump webpack-bundle-analyzer from 5.2.0 to 5.3.0 | [#2353]
|
||||||
|
- Fix all zeros after 16384 bytes with Blake3 [@zachbowden] [@GCHQDeveloper581] | [#2351]
|
||||||
|
|
||||||
## [11.0.0] - 2026-04-28
|
## [11.0.0] - 2026-04-28
|
||||||
- Revert sitemap to v8.0.X to fix build/deploy on master [@GCHQDeveloper581] | [#2348]
|
- Revert sitemap to v8.0.X to fix build/deploy on master [@GCHQDeveloper581] | [#2348]
|
||||||
- Node version update from 22 to 24 [@lzandman] [@GCHQDeveloper581] | [#2347]
|
- Node version update from 22 to 24 [@lzandman] [@GCHQDeveloper581] | [#2347]
|
||||||
@ -638,6 +718,8 @@ Breaking changes:
|
|||||||
## [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)
|
||||||
|
|
||||||
|
[11.2.0]: https://github.com/gchq/CyberChef/releases/tag/v11.2.0
|
||||||
|
[11.1.0]: https://github.com/gchq/CyberChef/releases/tag/v11.1.0
|
||||||
[11.0.0]: https://github.com/gchq/CyberChef/releases/tag/v11.0.0
|
[11.0.0]: https://github.com/gchq/CyberChef/releases/tag/v11.0.0
|
||||||
[10.24.0]: https://github.com/gchq/CyberChef/releases/tag/v10.24.0
|
[10.24.0]: https://github.com/gchq/CyberChef/releases/tag/v10.24.0
|
||||||
[10.23.0]: https://github.com/gchq/CyberChef/releases/tag/v10.23.0
|
[10.23.0]: https://github.com/gchq/CyberChef/releases/tag/v10.23.0
|
||||||
@ -655,7 +737,7 @@ Breaking changes:
|
|||||||
[10.11.0]: https://github.com/gchq/CyberChef/releases/tag/v10.11.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.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.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.8.0]: https://github.com/gchq/CyberChef/releases/tag/v10.8.0
|
||||||
[10.7.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.6.0]: https://github.com/gchq/CyberChef/releases/tag/v10.6.0
|
||||||
[10.5.0]: https://github.com/gchq/CyberChef/releases/tag/v10.5.0
|
[10.5.0]: https://github.com/gchq/CyberChef/releases/tag/v10.5.0
|
||||||
@ -923,6 +1005,19 @@ Breaking changes:
|
|||||||
[@hsolberg]: https://github.com/hsolberg
|
[@hsolberg]: https://github.com/hsolberg
|
||||||
[@lzandman]: https://github.com/lzandman
|
[@lzandman]: https://github.com/lzandman
|
||||||
[@engin0223]: https://github.com/engin0223
|
[@engin0223]: https://github.com/engin0223
|
||||||
|
[@Fufu-btw]: https://github.com/Fufu-btw
|
||||||
|
[@jl5193]: https://github.com/jl5193
|
||||||
|
[@andreasrtv]: https://github.com/andreasrtv
|
||||||
|
[@HarelKatz]: https://github.com/HarelKatz
|
||||||
|
[@SyedIshmumAhnaf]: https://github.com/SyedIshmumAhnaf
|
||||||
|
[@Louis-Ladd]: https://github.com/Louis-Ladd
|
||||||
|
[@Blank0120]: https://github.com/Blank0120
|
||||||
|
[@zachbowden]: https://github.com/zachbowden
|
||||||
|
[@dweep-js]: https://github.com/dweep-js
|
||||||
|
[@Zish19]: https://github.com/Zish19
|
||||||
|
[@qa2me]: https://github.com/qa2me
|
||||||
|
[@heapframe]: https://github.com/heapframe
|
||||||
|
[@skyswordw]: https://github.com/skyswordw
|
||||||
|
|
||||||
|
|
||||||
[8ad18b]: https://github.com/gchq/CyberChef/commit/8ad18bc7db6d9ff184ba3518686293a7685bf7b7
|
[8ad18b]: https://github.com/gchq/CyberChef/commit/8ad18bc7db6d9ff184ba3518686293a7685bf7b7
|
||||||
@ -1220,4 +1315,78 @@ Breaking changes:
|
|||||||
[#2273]: https://github.com/gchq/CyberChef/pull/2273
|
[#2273]: https://github.com/gchq/CyberChef/pull/2273
|
||||||
[#2342]: https://github.com/gchq/CyberChef/pull/2342
|
[#2342]: https://github.com/gchq/CyberChef/pull/2342
|
||||||
[#1922]: https://github.com/gchq/CyberChef/pull/1922
|
[#1922]: https://github.com/gchq/CyberChef/pull/1922
|
||||||
|
[#2557]: https://github.com/gchq/CyberChef/pull/2557
|
||||||
|
[#2552]: https://github.com/gchq/CyberChef/pull/2552
|
||||||
|
[#2551]: https://github.com/gchq/CyberChef/pull/2551
|
||||||
|
[#2550]: https://github.com/gchq/CyberChef/pull/2550
|
||||||
|
[#2553]: https://github.com/gchq/CyberChef/pull/2553
|
||||||
|
[#2547]: https://github.com/gchq/CyberChef/pull/2547
|
||||||
|
[#2545]: https://github.com/gchq/CyberChef/pull/2545
|
||||||
|
[#2543]: https://github.com/gchq/CyberChef/pull/2543
|
||||||
|
[#2539]: https://github.com/gchq/CyberChef/pull/2539
|
||||||
|
[#1431]: https://github.com/gchq/CyberChef/pull/1431
|
||||||
|
[#2527]: https://github.com/gchq/CyberChef/pull/2527
|
||||||
|
[#2502]: https://github.com/gchq/CyberChef/pull/2502
|
||||||
|
[#2391]: https://github.com/gchq/CyberChef/pull/2391
|
||||||
|
[#2471]: https://github.com/gchq/CyberChef/pull/2471
|
||||||
|
[#2497]: https://github.com/gchq/CyberChef/pull/2497
|
||||||
|
[#2500]: https://github.com/gchq/CyberChef/pull/2500
|
||||||
|
[#2499]: https://github.com/gchq/CyberChef/pull/2499
|
||||||
|
[#2498]: https://github.com/gchq/CyberChef/pull/2498
|
||||||
|
[#2143]: https://github.com/gchq/CyberChef/pull/2143
|
||||||
|
[#2341]: https://github.com/gchq/CyberChef/pull/2341
|
||||||
|
[#2478]: https://github.com/gchq/CyberChef/pull/2478
|
||||||
|
[#2479]: https://github.com/gchq/CyberChef/pull/2479
|
||||||
|
[#2463]: https://github.com/gchq/CyberChef/pull/2463
|
||||||
|
[#2468]: https://github.com/gchq/CyberChef/pull/2468
|
||||||
|
[#2385]: https://github.com/gchq/CyberChef/pull/2385
|
||||||
|
[#2467]: https://github.com/gchq/CyberChef/pull/2467
|
||||||
|
[#2443]: https://github.com/gchq/CyberChef/pull/2443
|
||||||
|
[#2417]: https://github.com/gchq/CyberChef/pull/2417
|
||||||
|
[#2461]: https://github.com/gchq/CyberChef/pull/2461
|
||||||
|
[#2438]: https://github.com/gchq/CyberChef/pull/2438
|
||||||
|
[#2439]: https://github.com/gchq/CyberChef/pull/2439
|
||||||
|
[#2441]: https://github.com/gchq/CyberChef/pull/2441
|
||||||
|
[#2442]: https://github.com/gchq/CyberChef/pull/2442
|
||||||
|
[#2425]: https://github.com/gchq/CyberChef/pull/2425
|
||||||
|
[#2428]: https://github.com/gchq/CyberChef/pull/2428
|
||||||
|
[#2429]: https://github.com/gchq/CyberChef/pull/2429
|
||||||
|
[#2430]: https://github.com/gchq/CyberChef/pull/2430
|
||||||
|
[#2431]: https://github.com/gchq/CyberChef/pull/2431
|
||||||
|
[#2412]: https://github.com/gchq/CyberChef/pull/2412
|
||||||
|
[#2339]: https://github.com/gchq/CyberChef/pull/2339
|
||||||
|
[#2389]: https://github.com/gchq/CyberChef/pull/2389
|
||||||
|
[#2403]: https://github.com/gchq/CyberChef/pull/2403
|
||||||
|
[#2402]: https://github.com/gchq/CyberChef/pull/2402
|
||||||
|
[#2401]: https://github.com/gchq/CyberChef/pull/2401
|
||||||
|
[#2387]: https://github.com/gchq/CyberChef/pull/2387
|
||||||
|
[#2384]: https://github.com/gchq/CyberChef/pull/2384
|
||||||
|
[#2386]: https://github.com/gchq/CyberChef/pull/2386
|
||||||
|
[#2388]: https://github.com/gchq/CyberChef/pull/2388
|
||||||
|
[#2378]: https://github.com/gchq/CyberChef/pull/2378
|
||||||
|
[#2379]: https://github.com/gchq/CyberChef/pull/2379
|
||||||
|
[#2372]: https://github.com/gchq/CyberChef/pull/2372
|
||||||
|
[#2368]: https://github.com/gchq/CyberChef/pull/2368
|
||||||
|
[#2370]: https://github.com/gchq/CyberChef/pull/2370
|
||||||
|
[#2371]: https://github.com/gchq/CyberChef/pull/2371
|
||||||
|
[#2369]: https://github.com/gchq/CyberChef/pull/2369
|
||||||
|
[#1882]: https://github.com/gchq/CyberChef/pull/1882
|
||||||
|
[#2354]: https://github.com/gchq/CyberChef/pull/2354
|
||||||
|
[#2363]: https://github.com/gchq/CyberChef/pull/2363
|
||||||
|
[#2364]: https://github.com/gchq/CyberChef/pull/2364
|
||||||
|
[#2365]: https://github.com/gchq/CyberChef/pull/2365
|
||||||
|
[#2366]: https://github.com/gchq/CyberChef/pull/2366
|
||||||
|
[#2367]: https://github.com/gchq/CyberChef/pull/2367
|
||||||
|
[#2361]: https://github.com/gchq/CyberChef/pull/2361
|
||||||
|
[#2332]: https://github.com/gchq/CyberChef/pull/2332
|
||||||
|
[#2353]: https://github.com/gchq/CyberChef/pull/2353
|
||||||
|
[#2351]: https://github.com/gchq/CyberChef/pull/2351
|
||||||
|
[#2569]: https://github.com/gchq/CyberChef/pull/2569
|
||||||
|
[#2566]: https://github.com/gchq/CyberChef/pull/2566
|
||||||
|
[#2542]: https://github.com/gchq/CyberChef/pull/2542
|
||||||
|
[#2022]: https://github.com/gchq/CyberChef/pull/2022
|
||||||
|
[#2409]: https://github.com/gchq/CyberChef/pull/2409
|
||||||
|
[#2404]: https://github.com/gchq/CyberChef/pull/2404
|
||||||
|
[#2458]: https://github.com/gchq/CyberChef/pull/2458
|
||||||
|
[#2514]: https://github.com/gchq/CyberChef/pull/2514
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
# Modifier --platform=$BUILDPLATFORM limits the platform to "BUILDPLATFORM" during buildx multi-platform builds
|
# Modifier --platform=$BUILDPLATFORM limits the platform to "BUILDPLATFORM" during buildx multi-platform builds
|
||||||
# This is because npm "chromedriver" package is not compatiable with all platforms
|
# This is because npm "chromedriver" package is not compatiable with all platforms
|
||||||
# For more info see: https://docs.docker.com/build/building/multi-platform/#cross-compilation
|
# For more info see: https://docs.docker.com/build/building/multi-platform/#cross-compilation
|
||||||
FROM --platform=$BUILDPLATFORM node:24-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14 AS builder
|
FROM --platform=$BUILDPLATFORM node:24-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd AS builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ RUN npm run build
|
|||||||
#########################################
|
#########################################
|
||||||
# Package static build files into nginx #
|
# Package static build files into nginx #
|
||||||
#########################################
|
#########################################
|
||||||
FROM nginxinc/nginx-unprivileged:stable-alpine@sha256:0a1e718ff1e1a22fc519d0c2e5b6872681f01e37c8a2817ec43ce6e716103929 AS cyberchef
|
FROM nginxinc/nginx-unprivileged:stable-alpine@sha256:fd3314e343bad2de4e1127ef58be122abbfa7e09572fa46ae62fcddb6b3f21c5 AS cyberchef
|
||||||
|
|
||||||
LABEL maintainer="GCHQ <oss@gchq.gov.uk>"
|
LABEL maintainer="GCHQ <oss@gchq.gov.uk>"
|
||||||
|
|
||||||
|
|||||||
@ -144,7 +144,8 @@ module.exports = function (grunt) {
|
|||||||
new BundleAnalyzerPlugin({
|
new BundleAnalyzerPlugin({
|
||||||
analyzerMode: "static",
|
analyzerMode: "static",
|
||||||
reportFilename: "BundleAnalyzerReport.html",
|
reportFilename: "BundleAnalyzerReport.html",
|
||||||
openAnalyzer: false
|
openAnalyzer: false,
|
||||||
|
excludeAssets: /.*Worker.js/
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
13
README.md
13
README.md
@ -12,13 +12,9 @@ CyberChef is a simple, intuitive web app for carrying out all manner of "cyber"
|
|||||||
|
|
||||||
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.
|
||||||
|
|
||||||
## Live demo
|
## Official website
|
||||||
|
|
||||||
CyberChef is still under active development. As a result, it shouldn't be considered a finished product. There is still testing and bug fixing to do, new features to be added and additional documentation to write. Please contribute!
|
[CyberChef's official website can be found here][1] - have fun!
|
||||||
|
|
||||||
Cryptographic operations in CyberChef should not be relied upon to provide security in any situation. No guarantee is offered for their correctness.
|
|
||||||
|
|
||||||
[A live demo can be found here][1] - have fun!
|
|
||||||
|
|
||||||
## Running Locally with Docker
|
## Running Locally with Docker
|
||||||
|
|
||||||
@ -124,6 +120,11 @@ CyberChef is built to support
|
|||||||
CyberChef is built to fully support Node.js `v24`. For more information, see the ["Node API" wiki page](https://github.com/gchq/CyberChef/wiki/Node-API)
|
CyberChef is built to fully support Node.js `v24`. For more information, see the ["Node API" wiki page](https://github.com/gchq/CyberChef/wiki/Node-API)
|
||||||
|
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
Please see the [CyberChef security policy](./SECURITY.md).
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Contributing a new operation to CyberChef is super easy! The quickstart script will walk you through the process. If you can write basic JavaScript, you can write a CyberChef operation.
|
Contributing a new operation to CyberChef is super easy! The quickstart script will walk you through the process. If you can write basic JavaScript, you can write a CyberChef operation.
|
||||||
|
|||||||
30
SECURITY.md
30
SECURITY.md
@ -1,26 +1,18 @@
|
|||||||
# Security Policy
|
# Security Policy
|
||||||
|
|
||||||
## Supported Versions
|
## Support
|
||||||
|
|
||||||
CyberChef is supported on a best endeavours basis. Patches will be applied to
|
CyberChef is supported on a best endeavours basis.
|
||||||
the latest version rather than retroactively to older versions. To ensure you
|
Patches will be applied to the latest version rather than retroactively to older versions.
|
||||||
are using the most secure version of CyberChef, please make sure you have the
|
To ensure you are using the most secure version of CyberChef, please make sure you have the [latest release](https://github.com/gchq/CyberChef/releases/latest). [The official website](https://gchq.github.io/CyberChef/) is always up to date.
|
||||||
[latest release](https://github.com/gchq/CyberChef/releases/latest). The
|
|
||||||
official [live demo](https://gchq.github.io/CyberChef/) is always up to date.
|
No guarantee is offered for the correctness or security of CyberChef. In paticular, the security of cryptographic operations should not be relied upon.
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
In most scenarios, the most appropriate way to report a vulnerability is to
|
If you discover a vulnerability in CyberChef, please do not publicly disclose it, and do not create a GitHub issue.
|
||||||
[raise a new issue](https://github.com/gchq/CyberChef/issues/new/choose)
|
|
||||||
describing the problem in as much detail as possible, ideally with examples.
|
|
||||||
This will obviously be public. If you feel that the vulnerability is
|
|
||||||
significant enough to warrant a private disclosure, please email
|
|
||||||
[oss@gchq.gov.uk](mailto:oss@gchq.gov.uk) and
|
|
||||||
[n1474335@gmail.com](mailto:n1474335@gmail.com).
|
|
||||||
|
|
||||||
Disclosures of vulnerabilities in CyberChef are always welcomed. Whilst we aim
|
Instead, send an email as soon as possible to [CyberChefSecurity@gchq.gov.uk](mailto:CyberChefSecurity@gchq.gov.uk).
|
||||||
to write clean and secure code free from bugs, we recognise that this is an open
|
The report will be acknowledged and actioned urgently by the CyberChef maintainers.
|
||||||
source project written by analysts in their spare time, relying on dozens of
|
|
||||||
open source libraries that are modified and updated on a regular basis. We hope
|
If you do not receive a timely acknowledgement, please notify [oss@gchq.gov.uk](mailto:oss@gchq.gov.uk) and [CyberChef@gchq.gov.uk](mailto:CyberChef@gchq.gov.uk) of your vulnerability report.
|
||||||
that the community will continue to support us as we endeavour to maintain and
|
|
||||||
develop this tool together.
|
|
||||||
|
|||||||
1319
package-lock.json
generated
1319
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
51
package.json
51
package.json
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "cyberchef",
|
"name": "cyberchef",
|
||||||
"version": "11.0.0",
|
"version": "11.2.0",
|
||||||
"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": "GCHQ <CyberChef@gchq.gov.uk>",
|
||||||
"homepage": "https://gchq.github.io/CyberChef",
|
"homepage": "https://gchq.github.io/CyberChef",
|
||||||
"copyright": "Crown copyright 2016",
|
"copyright": "Crown copyright 2016",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
@ -44,13 +44,13 @@
|
|||||||
"@babel/plugin-transform-runtime": "^7.29.7",
|
"@babel/plugin-transform-runtime": "^7.29.7",
|
||||||
"@babel/preset-env": "^7.29.7",
|
"@babel/preset-env": "^7.29.7",
|
||||||
"@babel/runtime": "^7.29.7",
|
"@babel/runtime": "^7.29.7",
|
||||||
"@codemirror/commands": "^6.10.3",
|
"@codemirror/commands": "^6.10.4",
|
||||||
"@codemirror/language": "^6.12.3",
|
"@codemirror/language": "^6.12.4",
|
||||||
"@codemirror/search": "^6.7.0",
|
"@codemirror/search": "^6.7.1",
|
||||||
"@codemirror/state": "^6.5.4",
|
"@codemirror/state": "^6.5.4",
|
||||||
"@codemirror/view": "^6.43.0",
|
"@codemirror/view": "^6.43.4",
|
||||||
"@puppeteer/browsers": "3.0.4",
|
"@puppeteer/browsers": "3.0.6",
|
||||||
"autoprefixer": "^10.5.0",
|
"autoprefixer": "^10.5.2",
|
||||||
"babel-loader": "^10.1.1",
|
"babel-loader": "^10.1.1",
|
||||||
"base64-loader": "^1.0.0",
|
"base64-loader": "^1.0.0",
|
||||||
"chromedriver": "^148.0.4",
|
"chromedriver": "^148.0.4",
|
||||||
@ -59,11 +59,11 @@
|
|||||||
"compression-webpack-plugin": "^12.0.0",
|
"compression-webpack-plugin": "^12.0.0",
|
||||||
"copy-webpack-plugin": "^14.0.0",
|
"copy-webpack-plugin": "^14.0.0",
|
||||||
"core-js": "^3.49.0",
|
"core-js": "^3.49.0",
|
||||||
"cspell": "^9.7.0",
|
"cspell": "^10.0.1",
|
||||||
"css-loader": "^7.1.4",
|
"css-loader": "^7.1.4",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "^9.39.4",
|
||||||
"eslint-plugin-jsdoc": "^50.8.0",
|
"eslint-plugin-jsdoc": "^50.8.0",
|
||||||
"globals": "^17.6.0",
|
"globals": "^17.7.0",
|
||||||
"grunt": "^1.6.2",
|
"grunt": "^1.6.2",
|
||||||
"grunt-chmod": "~1.1.1",
|
"grunt-chmod": "~1.1.1",
|
||||||
"grunt-concurrent": "^3.0.0",
|
"grunt-concurrent": "^3.0.0",
|
||||||
@ -71,25 +71,25 @@
|
|||||||
"grunt-contrib-connect": "^5.0.1",
|
"grunt-contrib-connect": "^5.0.1",
|
||||||
"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": "^25.0.0",
|
"grunt-eslint": "^26.0.0",
|
||||||
"grunt-exec": "~3.0.0",
|
"grunt-exec": "~3.0.0",
|
||||||
"grunt-webpack": "^6.0.0",
|
"grunt-webpack": "^8.0.0",
|
||||||
"grunt-zip": "^1.0.0",
|
"grunt-zip": "^1.0.0",
|
||||||
"html-webpack-plugin": "^5.6.7",
|
"html-webpack-plugin": "^5.6.7",
|
||||||
"imports-loader": "^5.0.0",
|
"imports-loader": "^5.0.0",
|
||||||
"mini-css-extract-plugin": "2.10.2",
|
"mini-css-extract-plugin": "2.10.2",
|
||||||
"modify-source-webpack-plugin": "^4.1.0",
|
"modify-source-webpack-plugin": "^4.1.0",
|
||||||
"nightwatch": "^3.16.0",
|
"nightwatch": "^3.16.0",
|
||||||
"postcss": "^8.5.15",
|
"postcss": "^8.5.16",
|
||||||
"postcss-css-variables": "^0.19.0",
|
"postcss-css-variables": "^0.19.0",
|
||||||
"postcss-import": "^16.1.1",
|
"postcss-import": "^16.1.1",
|
||||||
"postcss-loader": "^8.2.1",
|
"postcss-loader": "^8.2.1",
|
||||||
"prompt": "^1.3.0",
|
"prompt": "^1.3.0",
|
||||||
"sitemap": "^9.0.1",
|
"sitemap": "^9.0.1",
|
||||||
"terser": "^5.48.0",
|
"terser": "^5.48.0",
|
||||||
"webpack": "^5.107.2",
|
"webpack": "^5.108.3",
|
||||||
"webpack-bundle-analyzer": "^5.3.0",
|
"webpack-bundle-analyzer": "^5.3.0",
|
||||||
"webpack-dev-server": "^5.2.4",
|
"webpack-dev-server": "^5.2.5",
|
||||||
"webpack-node-externals": "^3.0.0",
|
"webpack-node-externals": "^3.0.0",
|
||||||
"worker-loader": "^3.0.8"
|
"worker-loader": "^3.0.8"
|
||||||
},
|
},
|
||||||
@ -105,15 +105,15 @@
|
|||||||
"assert": "^2.1.0",
|
"assert": "^2.1.0",
|
||||||
"avsc": "^5.7.9",
|
"avsc": "^5.7.9",
|
||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^3.0.3",
|
||||||
"bignumber.js": "^9.3.1",
|
"bignumber.js": "^11.1.4",
|
||||||
"blakejs": "^1.2.1",
|
"blakejs": "^1.2.1",
|
||||||
"bootstrap": "4.6.2",
|
"bootstrap": "4.6.2",
|
||||||
"bootstrap-colorpicker": "^3.4.0",
|
"bootstrap-colorpicker": "^3.4.0",
|
||||||
"bootstrap-material-design": "^4.1.3",
|
"bootstrap-material-design": "^4.1.3",
|
||||||
"browserify-zlib": "^0.2.0",
|
"browserify-zlib": "^0.2.0",
|
||||||
"bson": "^7.2.0",
|
"bson": "^7.3.1",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"cbor": "9.0.2",
|
"cbor": "10.0.12",
|
||||||
"chi-squared": "^1.1.0",
|
"chi-squared": "^1.1.0",
|
||||||
"codepage": "^1.15.0",
|
"codepage": "^1.15.0",
|
||||||
"crypto-api": "^0.8.5",
|
"crypto-api": "^0.8.5",
|
||||||
@ -122,8 +122,8 @@
|
|||||||
"ctph.js": "0.0.5",
|
"ctph.js": "0.0.5",
|
||||||
"d3": "7.9.0",
|
"d3": "7.9.0",
|
||||||
"d3-hexbin": "^0.2.2",
|
"d3-hexbin": "^0.2.2",
|
||||||
"diff": "^5.2.2",
|
"diff": "^9.0.0",
|
||||||
"dompurify": "^3.4.7",
|
"dompurify": "^3.4.11",
|
||||||
"es6-promisify": "^7.0.0",
|
"es6-promisify": "^7.0.0",
|
||||||
"escodegen": "^2.1.0",
|
"escodegen": "^2.1.0",
|
||||||
"esprima": "^4.0.1",
|
"esprima": "^4.0.1",
|
||||||
@ -139,6 +139,7 @@
|
|||||||
"jimp": "1.6.0",
|
"jimp": "1.6.0",
|
||||||
"jq-web": "^0.5.1",
|
"jq-web": "^0.5.1",
|
||||||
"jquery": "3.7.1",
|
"jquery": "3.7.1",
|
||||||
|
"js-ascon": "^1.3.0",
|
||||||
"js-sha3": "^0.9.3",
|
"js-sha3": "^0.9.3",
|
||||||
"jsesc": "^3.1.0",
|
"jsesc": "^3.1.0",
|
||||||
"json5": "^2.2.3",
|
"json5": "^2.2.3",
|
||||||
@ -169,7 +170,7 @@
|
|||||||
"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.6.2",
|
"protobufjs": "^8.6.5",
|
||||||
"punycode.js": "^2.3.1",
|
"punycode.js": "^2.3.1",
|
||||||
"qr-image": "^3.2.0",
|
"qr-image": "^3.2.0",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
@ -178,15 +179,15 @@
|
|||||||
"snackbarjs": "^1.1.0",
|
"snackbarjs": "^1.1.0",
|
||||||
"sortablejs": "^1.15.7",
|
"sortablejs": "^1.15.7",
|
||||||
"split.js": "^1.6.5",
|
"split.js": "^1.6.5",
|
||||||
"sql-formatter": "^15.8.0",
|
"sql-formatter": "^15.8.2",
|
||||||
"ssdeep.js": "0.0.3",
|
"ssdeep.js": "0.0.3",
|
||||||
"stream-browserify": "^3.0.0",
|
"stream-browserify": "^3.0.0",
|
||||||
"tesseract.js": "^6.0.1",
|
"tesseract.js": "^7.0.0",
|
||||||
"ua-parser-js": "^1.0.41",
|
"ua-parser-js": "^2.0.10",
|
||||||
"unorm": "^1.6.0",
|
"unorm": "^1.6.0",
|
||||||
"url": "^0.11.4",
|
"url": "^0.11.4",
|
||||||
"utf8": "^3.0.0",
|
"utf8": "^3.0.0",
|
||||||
"uuid": "^14.0.0",
|
"uuid": "^14.0.1",
|
||||||
"vkbeautify": "^0.99.3",
|
"vkbeautify": "^0.99.3",
|
||||||
"xpath": "0.0.34",
|
"xpath": "0.0.34",
|
||||||
"xregexp": "^5.1.2",
|
"xregexp": "^5.1.2",
|
||||||
|
|||||||
@ -138,6 +138,8 @@ class Chef {
|
|||||||
|
|
||||||
if (!highlights) return false;
|
if (!highlights) return false;
|
||||||
|
|
||||||
|
if (direction === "reverse") highlights.reverse();
|
||||||
|
|
||||||
for (let i = 0; i < highlights.length; i++) {
|
for (let i = 0; i < highlights.length; i++) {
|
||||||
// Remove multiple highlights before processing again
|
// Remove multiple highlights before processing again
|
||||||
pos = [pos[0]];
|
pos = [pos[0]];
|
||||||
|
|||||||
@ -292,11 +292,7 @@ class Dish {
|
|||||||
and reinitialise it as a BigNumber object.
|
and reinitialise it as a BigNumber object.
|
||||||
*/
|
*/
|
||||||
if (Object.keys(this.value).sort().equals(["c", "e", "s"])) {
|
if (Object.keys(this.value).sort().equals(["c", "e", "s"])) {
|
||||||
const temp = new BigNumber();
|
this.value = new BigNumber({ s: this.value.s, e: this.value.e, c: this.value.c, _isBigNumber: true});
|
||||||
temp.c = this.value.c;
|
|
||||||
temp.e = this.value.e;
|
|
||||||
temp.s = this.value.s;
|
|
||||||
this.value = temp;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -32,6 +32,8 @@ class Ingredient {
|
|||||||
this.min = null;
|
this.min = null;
|
||||||
this.max = null;
|
this.max = null;
|
||||||
this.step = 1;
|
this.step = 1;
|
||||||
|
this.integer = false;
|
||||||
|
this.allowEmpty = true;
|
||||||
|
|
||||||
if (ingredientConfig) {
|
if (ingredientConfig) {
|
||||||
this._parseConfig(ingredientConfig);
|
this._parseConfig(ingredientConfig);
|
||||||
@ -59,6 +61,96 @@ class Ingredient {
|
|||||||
this.min = ingredientConfig.min;
|
this.min = ingredientConfig.min;
|
||||||
this.max = ingredientConfig.max;
|
this.max = ingredientConfig.max;
|
||||||
this.step = ingredientConfig.step;
|
this.step = ingredientConfig.step;
|
||||||
|
this.integer = typeof ingredientConfig.integer !== "undefined" ? !!ingredientConfig.integer : false;
|
||||||
|
this.allowEmpty = typeof ingredientConfig.allowEmpty !== "undefined" ? !!ingredientConfig.allowEmpty : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the given value against the constraints of this ingredient.
|
||||||
|
*
|
||||||
|
* @param {*} val
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
validate(val) {
|
||||||
|
if (this.disabled) return true;
|
||||||
|
|
||||||
|
let checkVal = val;
|
||||||
|
if (checkVal === null || checkVal === undefined) {
|
||||||
|
checkVal = this.defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.type === "toggleString" && checkVal && typeof checkVal === "object" && "string" in checkVal) {
|
||||||
|
checkVal = checkVal.string;
|
||||||
|
}
|
||||||
|
if (this.type === "option" && Array.isArray(checkVal)) {
|
||||||
|
checkVal = checkVal[this.defaultIndex ?? 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. check if empty
|
||||||
|
let isEmpty = false;
|
||||||
|
if (checkVal === null || checkVal === undefined || checkVal === "") {
|
||||||
|
isEmpty = true;
|
||||||
|
} else if (typeof checkVal.length === "number" && checkVal.length === 0) {
|
||||||
|
isEmpty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEmpty) {
|
||||||
|
let isAllowedOptionEmpty = false;
|
||||||
|
if (this.type === "option" && Array.isArray(this.defaultValue)) {
|
||||||
|
isAllowedOptionEmpty = this.defaultValue.includes("");
|
||||||
|
}
|
||||||
|
if (this.allowEmpty === false || (this.type === "option" && !isAllowedOptionEmpty)) {
|
||||||
|
throw new OperationError(`${this.name} cannot be empty.`);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. maxLength check
|
||||||
|
if (typeof this.maxLength === "number" && checkVal !== null && checkVal !== undefined) {
|
||||||
|
if (typeof checkVal === "string" && checkVal.length > this.maxLength) {
|
||||||
|
throw new OperationError(`${this.name} length cannot exceed ${this.maxLength}.`);
|
||||||
|
}
|
||||||
|
if (Array.isArray(checkVal) && checkVal.length > this.maxLength) {
|
||||||
|
throw new OperationError(`${this.name} length cannot exceed ${this.maxLength}.`);
|
||||||
|
}
|
||||||
|
if (checkVal instanceof Uint8Array && checkVal.length > this.maxLength) {
|
||||||
|
throw new OperationError(`${this.name} length cannot exceed ${this.maxLength}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. number checks
|
||||||
|
if (this.type === "number") {
|
||||||
|
if (checkVal === null || checkVal === undefined || isNaN(checkVal)) {
|
||||||
|
throw new OperationError(`${this.name} must be a number.`);
|
||||||
|
}
|
||||||
|
if (this.integer && !Number.isInteger(checkVal)) {
|
||||||
|
throw new OperationError(`${this.name} must be an integer.`);
|
||||||
|
}
|
||||||
|
if (typeof this.min === "number" && checkVal < this.min) {
|
||||||
|
throw new OperationError(`${this.name} must be greater than or equal to ${this.min}.`);
|
||||||
|
}
|
||||||
|
if (typeof this.max === "number" && checkVal > this.max) {
|
||||||
|
throw new OperationError(`${this.name} must be less than or equal to ${this.max}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. option checks
|
||||||
|
if (this.type === "option") {
|
||||||
|
if (Array.isArray(this.defaultValue)) {
|
||||||
|
const permittedOptions = this.defaultValue.filter(opt => {
|
||||||
|
if (typeof opt !== "string") return false;
|
||||||
|
return !opt.match(/^\[\/?[a-z0-9 -()^]+\]$/i);
|
||||||
|
});
|
||||||
|
const valStr = (checkVal !== null && checkVal !== undefined) ? String(checkVal).toLowerCase() : "";
|
||||||
|
const matchedOption = permittedOptions.find(opt => opt.toLowerCase() === valStr);
|
||||||
|
if (!matchedOption) {
|
||||||
|
throw new OperationError(`${this.name} must be one of the following: ${permittedOptions.join(", ")}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -189,11 +189,30 @@ class Operation {
|
|||||||
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;
|
||||||
|
if (typeof ing.integer !== "undefined") conf.integer = ing.integer;
|
||||||
|
if (typeof ing.allowEmpty !== "undefined") conf.allowEmpty = ing.allowEmpty;
|
||||||
return conf;
|
return conf;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the operation's ingredients against their defined constraints.
|
||||||
|
*
|
||||||
|
* @param {Object[]} [args] - Optional list of argument values to validate. If not provided, validates the current ingredient values.
|
||||||
|
* @returns {boolean} - True if valid, throws an OperationError if invalid.
|
||||||
|
*/
|
||||||
|
validateIngredients(args) {
|
||||||
|
const values = args || this.ingValues;
|
||||||
|
this._ingList.forEach((ing, i) => {
|
||||||
|
if (i < values.length) {
|
||||||
|
ing.validate(values[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the value of the Operation as it should be displayed in a recipe config.
|
* Returns the value of the Operation as it should be displayed in a recipe config.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -212,6 +212,8 @@ class Recipe {
|
|||||||
self.sendProgressMessage(i + 1, this.opList.length);
|
self.sendProgressMessage(i + 1, this.opList.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
op.validateIngredients(op.ingValues);
|
||||||
|
|
||||||
if (op.flowControl) {
|
if (op.flowControl) {
|
||||||
// Package up the current state
|
// Package up the current state
|
||||||
let state = {
|
let state = {
|
||||||
@ -239,9 +241,11 @@ class Recipe {
|
|||||||
// 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");
|
||||||
|
this.lastRunOp = null;
|
||||||
return i;
|
return i;
|
||||||
} else if (err instanceof DishError || err?.type === "DishError") {
|
} else if (err instanceof DishError || err?.type === "DishError") {
|
||||||
dish.set(err.message, "string");
|
dish.set(err.message, "string");
|
||||||
|
this.lastRunOp = null;
|
||||||
return i;
|
return i;
|
||||||
} else {
|
} else {
|
||||||
const e = typeof err == "string" ? { message: err } : err;
|
const e = typeof err == "string" ? { message: err } : err;
|
||||||
|
|||||||
@ -115,12 +115,15 @@
|
|||||||
"SM4 Decrypt",
|
"SM4 Decrypt",
|
||||||
"RC6 Encrypt",
|
"RC6 Encrypt",
|
||||||
"RC6 Decrypt",
|
"RC6 Decrypt",
|
||||||
|
"Ascon Encrypt",
|
||||||
|
"Ascon Decrypt",
|
||||||
"GOST Encrypt",
|
"GOST Encrypt",
|
||||||
"GOST Decrypt",
|
"GOST Decrypt",
|
||||||
"GOST Sign",
|
"GOST Sign",
|
||||||
"GOST Verify",
|
"GOST Verify",
|
||||||
"GOST Key Wrap",
|
"GOST Key Wrap",
|
||||||
"GOST Key Unwrap",
|
"GOST Key Unwrap",
|
||||||
|
"ROR13",
|
||||||
"ROT13",
|
"ROT13",
|
||||||
"ROT13 Brute Force",
|
"ROT13 Brute Force",
|
||||||
"ROT47",
|
"ROT47",
|
||||||
@ -242,6 +245,7 @@
|
|||||||
"Bit shift right",
|
"Bit shift right",
|
||||||
"Rotate left",
|
"Rotate left",
|
||||||
"Rotate right",
|
"Rotate right",
|
||||||
|
"ROR13",
|
||||||
"ROT13",
|
"ROT13",
|
||||||
"ROT8000"
|
"ROT8000"
|
||||||
]
|
]
|
||||||
@ -446,6 +450,8 @@
|
|||||||
"BLAKE2b",
|
"BLAKE2b",
|
||||||
"BLAKE2s",
|
"BLAKE2s",
|
||||||
"BLAKE3",
|
"BLAKE3",
|
||||||
|
"Ascon Hash",
|
||||||
|
"Ascon MAC",
|
||||||
"GOST Hash",
|
"GOST Hash",
|
||||||
"Streebog",
|
"Streebog",
|
||||||
"SSDEEP",
|
"SSDEEP",
|
||||||
@ -558,7 +564,7 @@
|
|||||||
"Scatter chart",
|
"Scatter chart",
|
||||||
"Series chart",
|
"Series chart",
|
||||||
"Heatmap chart",
|
"Heatmap chart",
|
||||||
"Extract Audio Metadata"
|
"Render PDF"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -584,7 +590,8 @@
|
|||||||
"HTML To Text",
|
"HTML To Text",
|
||||||
"Generate Lorem Ipsum",
|
"Generate Lorem Ipsum",
|
||||||
"Numberwang",
|
"Numberwang",
|
||||||
"XKCD Random Number"
|
"XKCD Random Number",
|
||||||
|
"Automated Validation Test Op"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -602,4 +609,4 @@
|
|||||||
"Comment"
|
"Comment"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -58,3 +58,66 @@ fs.writeFileSync(
|
|||||||
code
|
code
|
||||||
);
|
);
|
||||||
console.log("Written operation index.");
|
console.log("Written operation index.");
|
||||||
|
|
||||||
|
// find all test files
|
||||||
|
const testsDir = path.join(process.cwd() + "/tests/operations/tests/");
|
||||||
|
const testObjs = [];
|
||||||
|
fs.readdirSync(testsDir).forEach(file => {
|
||||||
|
if (!file.endsWith(".mjs")) return;
|
||||||
|
testObjs.push(file.split(".mjs")[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Construct test index file
|
||||||
|
code = `/**
|
||||||
|
* THIS FILE IS AUTOMATICALLY GENERATED BY src/core/config/scripts/generateOpsIndex.mjs
|
||||||
|
*
|
||||||
|
* @author john [john19696@protonmail.com]
|
||||||
|
* @author tlwr [toby@toby.codes]
|
||||||
|
* @author n1474335 [n1474335@gmail.com]
|
||||||
|
* @copyright Crown Copyright ${new Date().getUTCFullYear()}
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
setLongTestFailure,
|
||||||
|
logTestReport,
|
||||||
|
} from "../lib/utils.mjs";
|
||||||
|
|
||||||
|
import "../lib/wasmFetchPolyfill.mjs";
|
||||||
|
|
||||||
|
import TestRegister from "../lib/TestRegister.mjs";
|
||||||
|
`;
|
||||||
|
|
||||||
|
testObjs.forEach(obj => {
|
||||||
|
if (obj !== "SplitColourChannels")
|
||||||
|
code += `import "./tests/${obj}.mjs";\n`;
|
||||||
|
else
|
||||||
|
code += `// Cannot test operations that use the File type yet
|
||||||
|
// import "./tests/SplitColourChannels.mjs";\n`;
|
||||||
|
});
|
||||||
|
|
||||||
|
code += `
|
||||||
|
|
||||||
|
const testStatus = {
|
||||||
|
allTestsPassing: true,
|
||||||
|
counts: {
|
||||||
|
total: 0,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setLongTestFailure();
|
||||||
|
|
||||||
|
const logOpsTestReport = logTestReport.bind(null, testStatus);
|
||||||
|
|
||||||
|
(async function() {
|
||||||
|
const results = await TestRegister.runTests();
|
||||||
|
logOpsTestReport(results);
|
||||||
|
})();
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Write tests file
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(testsDir, "../index.mjs"),
|
||||||
|
code
|
||||||
|
);
|
||||||
|
console.log("Written operation tests index.");
|
||||||
|
|||||||
@ -23,7 +23,7 @@ if (!fs.existsSync(dir)) {
|
|||||||
console.log("Example> node --experimental-modules src/core/config/scripts/newOperation.mjs");
|
console.log("Example> node --experimental-modules src/core/config/scripts/newOperation.mjs");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
const testDir = path.join(process.cwd() + "/tests/operations/tests/");
|
||||||
const ioTypes = ["string", "byteArray", "number", "html", "ArrayBuffer", "BigNumber", "JSON", "File", "List<File>"];
|
const ioTypes = ["string", "byteArray", "number", "html", "ArrayBuffer", "BigNumber", "JSON", "File", "List<File>"];
|
||||||
|
|
||||||
const schema = {
|
const schema = {
|
||||||
@ -123,6 +123,30 @@ prompt.get(schema, (err, result) => {
|
|||||||
return txt.charAt(0).toUpperCase() + txt.substr(1);
|
return txt.charAt(0).toUpperCase() + txt.substr(1);
|
||||||
}).replace(/[\s-()./]/g, "");
|
}).replace(/[\s-()./]/g, "");
|
||||||
|
|
||||||
|
const testTemplate = `/**
|
||||||
|
* ${moduleName} tests
|
||||||
|
*
|
||||||
|
* @author ${result.authorName} [${result.authorEmail}]
|
||||||
|
* @copyright Crown Copyright ${(new Date()).getFullYear()}
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "${result.opName}: test",
|
||||||
|
input: "Example input",
|
||||||
|
expectedOutput: "Expected output",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "${result.opName}",
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
`;
|
||||||
|
|
||||||
const template = `/**
|
const template = `/**
|
||||||
* @author ${result.authorName} [${result.authorEmail}]
|
* @author ${result.authorName} [${result.authorEmail}]
|
||||||
@ -218,13 +242,16 @@ export default ${moduleName};
|
|||||||
}
|
}
|
||||||
fs.writeFileSync(filename, template);
|
fs.writeFileSync(filename, template);
|
||||||
|
|
||||||
|
const testFilename = path.join(testDir, `./${moduleName}.mjs`);
|
||||||
|
fs.writeFileSync(testFilename, testTemplate);
|
||||||
|
|
||||||
console.log(`\nOperation template written to ${colors.green(filename)}`);
|
console.log(`\nOperation template written to ${colors.green(filename)}`);
|
||||||
|
console.log(`\nOperation test template written to ${colors.green(testFilename)}`);
|
||||||
console.log(`\nNext steps:
|
console.log(`\nNext steps:
|
||||||
1. Add your operation to ${colors.green("src/core/config/Categories.json")}
|
1. Add your operation to ${colors.green("src/core/config/Categories.json")}
|
||||||
2. Write your operation code.
|
2. Write your operation code in ${colors.green(filename)}
|
||||||
3. Write tests in ${colors.green("tests/operations/tests/")}
|
3. Write your operation test code in ${colors.green(testFilename)}
|
||||||
4. Run ${colors.cyan("npm run lint")} and ${colors.cyan("npm run test")}
|
4. Run ${colors.cyan("npm run lint")} and ${colors.cyan("npm run test")}
|
||||||
5. Submit a Pull Request to get your operation added to the official CyberChef repository.`);
|
5. Submit a Pull Request to get your operation added to the official CyberChef repository.`);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
class DishType {
|
class DishType {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Warn translations dont work without value from bind
|
* Warn translations don't work without value from bind
|
||||||
*/
|
*/
|
||||||
static checkForValue(value) {
|
static checkForValue(value) {
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Custom error type for handling operation that isnt included in node.js API
|
* Custom error type for handling operation that isn't included in node.js API
|
||||||
*
|
*
|
||||||
* @author d98762625 [d98762625@gmail.com]
|
* @author d98762625 [d98762625@gmail.com]
|
||||||
* @copyright Crown Copyright 2018
|
* @copyright Crown Copyright 2018
|
||||||
|
|||||||
@ -108,14 +108,17 @@ export function mean(data) {
|
|||||||
* @returns {BigNumber}
|
* @returns {BigNumber}
|
||||||
*/
|
*/
|
||||||
export function median(data) {
|
export function median(data) {
|
||||||
if ((data.length % 2) === 0 && data.length > 0) {
|
if (data.length > 0) {
|
||||||
data.sort(function(a, b) {
|
data.sort(function(a, b) {
|
||||||
return a.minus(b);
|
return a.minus(b);
|
||||||
});
|
});
|
||||||
const first = data[Math.floor(data.length / 2)];
|
|
||||||
const second = data[Math.floor(data.length / 2) - 1];
|
if ((data.length % 2) === 0) {
|
||||||
return mean([first, second]);
|
const first = data[Math.floor(data.length / 2)];
|
||||||
} else {
|
const second = data[Math.floor(data.length / 2) - 1];
|
||||||
|
return mean([first, second]);
|
||||||
|
}
|
||||||
|
|
||||||
return data[Math.floor(data.length / 2)];
|
return data[Math.floor(data.length / 2)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -153,7 +153,7 @@ export function getSeriesValues(input, recordDelimiter, fieldDelimiter, columnHe
|
|||||||
);
|
);
|
||||||
|
|
||||||
let xValues = new Set();
|
let xValues = new Set();
|
||||||
const series = {};
|
const series = Object.create(null);
|
||||||
|
|
||||||
values.forEach(row => {
|
values.forEach(row => {
|
||||||
const serie = row[0],
|
const serie = row[0],
|
||||||
@ -163,14 +163,14 @@ export function getSeriesValues(input, recordDelimiter, fieldDelimiter, columnHe
|
|||||||
if (Number.isNaN(val)) throw new OperationError("Values must be numbers in base 10.");
|
if (Number.isNaN(val)) throw new OperationError("Values must be numbers in base 10.");
|
||||||
|
|
||||||
xValues.add(xVal);
|
xValues.add(xVal);
|
||||||
if (typeof series[serie] === "undefined") series[serie] = {};
|
if (typeof series[serie] === "undefined") series[serie] = Object.create(null);
|
||||||
series[serie][xVal] = val;
|
series[serie][xVal] = val;
|
||||||
});
|
});
|
||||||
|
|
||||||
xValues = new Array(...xValues);
|
xValues = new Array(...xValues);
|
||||||
|
|
||||||
const seriesList = [];
|
const seriesList = [];
|
||||||
for (const seriesName in series) {
|
for (const seriesName of Object.keys(series)) {
|
||||||
const serie = series[seriesName];
|
const serie = series[seriesName];
|
||||||
seriesList.push({name: seriesName, data: serie});
|
seriesList.push({name: seriesName, data: serie});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import BigNumber from "bignumber.js";
|
import BigNumber from "bignumber.js";
|
||||||
import {toHexFast} from "../lib/Hex.mjs";
|
import {toHexFast} from "../lib/Hex.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively displays a JSON object as an HTML table
|
* Recursively displays a JSON object as an HTML table
|
||||||
@ -25,15 +26,16 @@ export function objToTable(obj, nested=false) {
|
|||||||
<th>Value</th>
|
<th>Value</th>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
|
|
||||||
for (const key in obj) {
|
for (const key of Object.keys(obj)) {
|
||||||
if (typeof obj[key] === "function")
|
const value = obj[key];
|
||||||
|
if (typeof value === "function")
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
html += `<tr><td style='word-wrap: break-word'>${key}</td>`;
|
html += `<tr><td style='word-wrap: break-word'>${Utils.escapeHtml(String(key))}</td>`;
|
||||||
if (typeof obj[key] === "object")
|
if (value !== null && typeof value === "object")
|
||||||
html += `<td style='padding: 0'>${objToTable(obj[key], true)}</td>`;
|
html += `<td style='padding: 0'>${objToTable(value, true)}</td>`;
|
||||||
else
|
else
|
||||||
html += `<td>${obj[key]}</td>`;
|
html += `<td>${Utils.escapeHtml(String(value))}</td>`;
|
||||||
html += "</tr>";
|
html += "</tr>";
|
||||||
}
|
}
|
||||||
html += "</table>";
|
html += "</table>";
|
||||||
|
|||||||
@ -33,20 +33,29 @@ export default class TLVParser {
|
|||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
getLength() {
|
getLength() {
|
||||||
|
let bytesInLength = this.bytesInLength;
|
||||||
|
let bigEndian = false;
|
||||||
|
|
||||||
if (this.basicEncodingRules) {
|
if (this.basicEncodingRules) {
|
||||||
const bit = this.input[this.location];
|
const firstLengthByte = this.input[this.location];
|
||||||
if (bit & 0x80) {
|
this.location++;
|
||||||
this.bytesInLength = bit & ~0x80;
|
|
||||||
|
if (firstLengthByte & 0x80) {
|
||||||
|
bytesInLength = firstLengthByte & ~0x80;
|
||||||
|
bigEndian = true;
|
||||||
} else {
|
} else {
|
||||||
this.location++;
|
return firstLengthByte & ~0x80;
|
||||||
return bit & ~0x80;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let length = 0;
|
let length = 0;
|
||||||
|
|
||||||
for (let i = 0; i < this.bytesInLength; i++) {
|
for (let i = 0; i < bytesInLength; i++) {
|
||||||
length += this.input[this.location] * Math.pow(Math.pow(2, 8), i);
|
if (bigEndian) {
|
||||||
|
length = (length << 8) + this.input[this.location];
|
||||||
|
} else {
|
||||||
|
length += this.input[this.location] * Math.pow(Math.pow(2, 8), i);
|
||||||
|
}
|
||||||
this.location++;
|
this.location++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
112
src/core/operations/AsconDecrypt.mjs
Normal file
112
src/core/operations/AsconDecrypt.mjs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* @author Medjedtxm
|
||||||
|
* @copyright Crown Copyright 2025
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
import { toHexFast } from "../lib/Hex.mjs";
|
||||||
|
import JsAscon from "js-ascon";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ascon Decrypt operation
|
||||||
|
*/
|
||||||
|
class AsconDecrypt extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AsconDecrypt constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Ascon Decrypt";
|
||||||
|
this.module = "Ciphers";
|
||||||
|
this.description = "Ascon-AEAD128 authenticated decryption as standardised in NIST SP 800-232. Decrypts ciphertext and verifies the authentication tag. Decryption will fail if the ciphertext or associated data has been tampered with.<br><br><b>Key:</b> Must be exactly 16 bytes (128 bits).<br><br><b>Nonce:</b> Must be exactly 16 bytes (128 bits). Must match the nonce used during encryption.<br><br><b>Associated Data:</b> Must match the associated data used during encryption. Any mismatch will cause authentication failure.";
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/Ascon_(cipher)";
|
||||||
|
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"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Associated Data",
|
||||||
|
"type": "toggleString",
|
||||||
|
"value": "",
|
||||||
|
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Input",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["Hex", "Raw"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Output",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["Raw", "Hex"]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
* @throws {OperationError} if invalid key or nonce length, or authentication fails
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const key = Utils.convertToByteArray(args[0].string, args[0].option),
|
||||||
|
nonce = Utils.convertToByteArray(args[1].string, args[1].option),
|
||||||
|
ad = Utils.convertToByteArray(args[2].string, args[2].option),
|
||||||
|
inputType = args[3],
|
||||||
|
outputType = args[4];
|
||||||
|
|
||||||
|
if (key.length !== 16) {
|
||||||
|
throw new OperationError(`Invalid key length: ${key.length} bytes.
|
||||||
|
|
||||||
|
Ascon-AEAD128 requires a key of exactly 16 bytes (128 bits).`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nonce.length !== 16) {
|
||||||
|
throw new OperationError(`Invalid nonce length: ${nonce.length} bytes.
|
||||||
|
|
||||||
|
Ascon-AEAD128 requires a nonce of exactly 16 bytes (128 bits).`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert input to byte array
|
||||||
|
const inputData = Utils.convertToByteArray(input, inputType);
|
||||||
|
|
||||||
|
const keyUint8 = new Uint8Array(key);
|
||||||
|
const nonceUint8 = new Uint8Array(nonce);
|
||||||
|
const adUint8 = new Uint8Array(ad);
|
||||||
|
const ciphertextUint8 = new Uint8Array(inputData);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Decrypt (returns Uint8Array containing plaintext)
|
||||||
|
const plaintext = JsAscon.decrypt(keyUint8, nonceUint8, adUint8, ciphertextUint8);
|
||||||
|
|
||||||
|
// Return in requested format
|
||||||
|
if (outputType === "Hex") {
|
||||||
|
return toHexFast(plaintext);
|
||||||
|
} else {
|
||||||
|
return Utils.arrayBufferToStr(Uint8Array.from(plaintext).buffer);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw new OperationError("Unable to decrypt: authentication failed. The ciphertext, key, nonce, or associated data may be incorrect or tampered with.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AsconDecrypt;
|
||||||
108
src/core/operations/AsconEncrypt.mjs
Normal file
108
src/core/operations/AsconEncrypt.mjs
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
/**
|
||||||
|
* @author Medjedtxm
|
||||||
|
* @copyright Crown Copyright 2025
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
import { toHexFast } from "../lib/Hex.mjs";
|
||||||
|
import JsAscon from "js-ascon";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ascon Encrypt operation
|
||||||
|
*/
|
||||||
|
class AsconEncrypt extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AsconEncrypt constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Ascon Encrypt";
|
||||||
|
this.module = "Ciphers";
|
||||||
|
this.description = "Ascon-AEAD128 authenticated encryption as standardised in NIST SP 800-232. Ascon is a family of lightweight authenticated encryption algorithms designed for constrained devices such as IoT sensors and embedded systems.<br><br><b>Key:</b> Must be exactly 16 bytes (128 bits).<br><br><b>Nonce:</b> Must be exactly 16 bytes (128 bits). Should be unique for each encryption with the same key. Never reuse a nonce with the same key.<br><br><b>Associated Data:</b> Optional additional data that is authenticated but not encrypted. Useful for including metadata like headers or timestamps.<br><br>The output includes both the ciphertext and a 128-bit authentication tag.";
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/Ascon_(cipher)";
|
||||||
|
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"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Associated Data",
|
||||||
|
"type": "toggleString",
|
||||||
|
"value": "",
|
||||||
|
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Input",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["Raw", "Hex"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Output",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["Hex", "Raw"]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
* @throws {OperationError} if invalid key or nonce length
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const key = Utils.convertToByteArray(args[0].string, args[0].option),
|
||||||
|
nonce = Utils.convertToByteArray(args[1].string, args[1].option),
|
||||||
|
ad = Utils.convertToByteArray(args[2].string, args[2].option),
|
||||||
|
inputType = args[3],
|
||||||
|
outputType = args[4];
|
||||||
|
|
||||||
|
if (key.length !== 16) {
|
||||||
|
throw new OperationError(`Invalid key length: ${key.length} bytes.
|
||||||
|
|
||||||
|
Ascon-AEAD128 requires a key of exactly 16 bytes (128 bits).`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nonce.length !== 16) {
|
||||||
|
throw new OperationError(`Invalid nonce length: ${nonce.length} bytes.
|
||||||
|
|
||||||
|
Ascon-AEAD128 requires a nonce of exactly 16 bytes (128 bits).`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert input to byte array
|
||||||
|
const inputData = Utils.convertToByteArray(input, inputType);
|
||||||
|
|
||||||
|
const keyUint8 = new Uint8Array(key);
|
||||||
|
const nonceUint8 = new Uint8Array(nonce);
|
||||||
|
const adUint8 = new Uint8Array(ad);
|
||||||
|
const inputUint8 = new Uint8Array(inputData);
|
||||||
|
|
||||||
|
// Encrypt (returns Uint8Array containing ciphertext + tag)
|
||||||
|
const ciphertext = JsAscon.encrypt(keyUint8, nonceUint8, adUint8, inputUint8);
|
||||||
|
|
||||||
|
// Return in requested format
|
||||||
|
if (outputType === "Hex") {
|
||||||
|
return toHexFast(ciphertext);
|
||||||
|
} else {
|
||||||
|
return Utils.arrayBufferToStr(Uint8Array.from(ciphertext).buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AsconEncrypt;
|
||||||
49
src/core/operations/AsconHash.mjs
Normal file
49
src/core/operations/AsconHash.mjs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* @author Medjedtxm
|
||||||
|
* @copyright Crown Copyright 2025
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import { toHexFast } from "../lib/Hex.mjs";
|
||||||
|
import JsAscon from "js-ascon";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ascon Hash operation
|
||||||
|
*/
|
||||||
|
class AsconHash extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AsconHash constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Ascon Hash";
|
||||||
|
this.module = "Crypto";
|
||||||
|
this.description = "Ascon-Hash256 produces a fixed 256-bit (32-byte) cryptographic hash as standardised in NIST SP 800-232. Ascon is a family of lightweight authenticated encryption and hashing algorithms designed for constrained devices such as IoT sensors and embedded systems.<br><br>The algorithm was selected by NIST in 2023 as the new standard for lightweight cryptography after a multi-year competition.";
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/Ascon_(cipher)";
|
||||||
|
this.inputType = "ArrayBuffer";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ArrayBuffer} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
|
||||||
|
const inputUint8 = new Uint8Array(input);
|
||||||
|
|
||||||
|
// Compute hash (returns Uint8Array)
|
||||||
|
const hashResult = JsAscon.hash(inputUint8);
|
||||||
|
|
||||||
|
// Convert to hex string
|
||||||
|
return toHexFast(hashResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AsconHash;
|
||||||
68
src/core/operations/AsconMAC.mjs
Normal file
68
src/core/operations/AsconMAC.mjs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/**
|
||||||
|
* @author Medjedtxm
|
||||||
|
* @copyright Crown Copyright 2025
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
import { toHexFast } from "../lib/Hex.mjs";
|
||||||
|
import AsconMac from "../vendor/ascon.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ascon MAC operation
|
||||||
|
*/
|
||||||
|
class AsconMAC extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AsconMAC constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Ascon MAC";
|
||||||
|
this.module = "Crypto";
|
||||||
|
this.description = "Ascon-Mac produces a 128-bit (16-byte) message authentication code as part of the Ascon family standardised by NIST in SP 800-232. It provides authentication for messages using a secret key, ensuring both data integrity and authenticity.<br><br>Ascon is designed for lightweight cryptography on constrained devices such as IoT sensors and embedded systems.";
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/Ascon_(cipher)";
|
||||||
|
this.inputType = "ArrayBuffer";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Key",
|
||||||
|
"type": "toggleString",
|
||||||
|
"value": "",
|
||||||
|
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ArrayBuffer} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
* @throws {OperationError} if invalid key length
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const keyArray = Utils.convertToByteArray(args[0].string, args[0].option);
|
||||||
|
|
||||||
|
if (keyArray.length !== 16) {
|
||||||
|
throw new OperationError(`Invalid key length: ${keyArray.length} bytes.
|
||||||
|
|
||||||
|
Ascon-Mac requires a key of exactly 16 bytes (128 bits).`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to Uint8Array for vendor Ascon implementation
|
||||||
|
const keyUint8 = new Uint8Array(keyArray);
|
||||||
|
const inputUint8 = new Uint8Array(input);
|
||||||
|
|
||||||
|
// Compute MAC (returns Uint8Array)
|
||||||
|
const macResult = AsconMac.mac(keyUint8, inputUint8);
|
||||||
|
|
||||||
|
// Convert to hex string
|
||||||
|
return toHexFast(macResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AsconMAC;
|
||||||
84
src/core/operations/AutomatedValidationTestOp.mjs
Normal file
84
src/core/operations/AutomatedValidationTestOp.mjs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* @author CyberChef
|
||||||
|
* @copyright Crown Copyright 2026
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automated validation test operation
|
||||||
|
*/
|
||||||
|
class AutomatedValidationTestOp extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AutomatedValidationTestOp constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Automated Validation Test Op";
|
||||||
|
this.module = "Default";
|
||||||
|
this.description = "Operation used specifically to test automated parameter validation.";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Integer Number",
|
||||||
|
"type": "number",
|
||||||
|
"value": 5,
|
||||||
|
"min": 5,
|
||||||
|
"max": 10,
|
||||||
|
"integer": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Real Number",
|
||||||
|
"type": "number",
|
||||||
|
"value": 1.5,
|
||||||
|
"min": 1.5,
|
||||||
|
"max": 5.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Non Empty String",
|
||||||
|
"type": "string",
|
||||||
|
"value": "hello",
|
||||||
|
"maxLength": 5,
|
||||||
|
"allowEmpty": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Empty Allowed String",
|
||||||
|
"type": "string",
|
||||||
|
"value": "",
|
||||||
|
"allowEmpty": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Non Empty Toggle String",
|
||||||
|
"type": "toggleString",
|
||||||
|
"value": {
|
||||||
|
"option": "Option A",
|
||||||
|
"string": "test"
|
||||||
|
},
|
||||||
|
"toggleValues": ["Option A", "Option B"],
|
||||||
|
"allowEmpty": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Option Ingredient",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["[Group 1]", "Option 1", "Option 2", "[/Group 1]", "[Group 2]", "Option 3", "[/Group 2]"],
|
||||||
|
"allowEmpty": false
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
return "Success";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AutomatedValidationTestOp;
|
||||||
@ -30,7 +30,11 @@ class BLAKE3 extends Operation {
|
|||||||
this.args = [
|
this.args = [
|
||||||
{
|
{
|
||||||
"name": "Size (bytes)",
|
"name": "Size (bytes)",
|
||||||
"type": "number"
|
"type": "number",
|
||||||
|
"value": 16,
|
||||||
|
"min": 1,
|
||||||
|
"max": 65535, // arbitrary limit to prevent resource exhaustion
|
||||||
|
"integer": true,
|
||||||
}, {
|
}, {
|
||||||
"name": "Key",
|
"name": "Key",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Operation from "../Operation.mjs";
|
import Operation from "../Operation.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
import bcrypt from "bcryptjs";
|
import bcrypt from "bcryptjs";
|
||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
|
|
||||||
@ -43,11 +44,16 @@ class BcryptCompare extends Operation {
|
|||||||
async run(input, args) {
|
async run(input, args) {
|
||||||
const hash = args[0];
|
const hash = args[0];
|
||||||
|
|
||||||
const match = await bcrypt.compare(input, hash, undefined, p => {
|
let match;
|
||||||
// Progress callback
|
try {
|
||||||
if (isWorkerEnvironment())
|
match = await bcrypt.compare(input, hash, undefined, p => {
|
||||||
self.sendStatusMessage(`Progress: ${(p * 100).toFixed(0)}%`);
|
// Progress callback
|
||||||
});
|
if (isWorkerEnvironment())
|
||||||
|
self.sendStatusMessage(`Progress: ${(p * 100).toFixed(0)}%`);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
throw new OperationError(err.toString());
|
||||||
|
}
|
||||||
|
|
||||||
return match ? "Match: " + input : "No match";
|
return match ? "Match: " + input : "No match";
|
||||||
|
|
||||||
|
|||||||
@ -27,7 +27,10 @@ class BitShiftLeft extends Operation {
|
|||||||
{
|
{
|
||||||
"name": "Amount",
|
"name": "Amount",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"value": 1
|
"value": 1,
|
||||||
|
"min": 0,
|
||||||
|
"max": 7,
|
||||||
|
"integer": true,
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -45,12 +45,15 @@ class DechunkHTTPResponse extends Operation {
|
|||||||
const lineEndingsLength = lineEndings.length;
|
const lineEndingsLength = lineEndings.length;
|
||||||
let chunkSize = parseInt(input.slice(0, chunkSizeEnd), 16);
|
let chunkSize = parseInt(input.slice(0, chunkSizeEnd), 16);
|
||||||
while (!isNaN(chunkSize)) {
|
while (!isNaN(chunkSize)) {
|
||||||
|
if (chunkSize === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
chunks.push(input.slice(chunkSizeEnd, chunkSize + chunkSizeEnd));
|
chunks.push(input.slice(chunkSizeEnd, chunkSize + chunkSizeEnd));
|
||||||
input = input.slice(chunkSizeEnd + chunkSize + lineEndingsLength);
|
input = input.slice(chunkSizeEnd + chunkSize + lineEndingsLength);
|
||||||
chunkSizeEnd = input.indexOf(lineEndings) + lineEndingsLength;
|
chunkSizeEnd = input.indexOf(lineEndings) + lineEndingsLength;
|
||||||
chunkSize = parseInt(input.slice(0, chunkSizeEnd), 16);
|
chunkSize = parseInt(input.slice(0, chunkSizeEnd), 16);
|
||||||
}
|
}
|
||||||
return chunks.join("") + input;
|
return chunks.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,9 +51,10 @@ class FromBase extends Operation {
|
|||||||
if (number.length === 1) return result;
|
if (number.length === 1) return result;
|
||||||
|
|
||||||
// Fractional part
|
// Fractional part
|
||||||
|
const radixBN = new BigNumber(radix);
|
||||||
for (let i = 0; i < number[1].length; i++) {
|
for (let i = 0; i < number[1].length; i++) {
|
||||||
const digit = new BigNumber(number[1][i], radix);
|
const digit = new BigNumber(number[1][i], radix);
|
||||||
result += digit.div(Math.pow(radix, i+1));
|
result = result.plus(digit.div(radixBN.pow(i + 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@ -50,6 +50,14 @@ class GenerateDeBruijnSequence extends Operation {
|
|||||||
throw new OperationError("Invalid alphabet size, required to be between 2 and 9 (inclusive).");
|
throw new OperationError("Invalid alphabet size, required to be between 2 and 9 (inclusive).");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Number.isInteger(k)) {
|
||||||
|
throw new OperationError("Invalid alphabet size, required to be integer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Number.isInteger(n)) {
|
||||||
|
throw new OperationError("Invalid key length, required to be integer.");
|
||||||
|
}
|
||||||
|
|
||||||
if (n < 2) {
|
if (n < 2) {
|
||||||
throw new OperationError("Invalid key length, required to be at least 2.");
|
throw new OperationError("Invalid key length, required to be at least 2.");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Operation from "../Operation.mjs";
|
import Operation from "../Operation.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
import * as OTPAuth from "otpauth";
|
import * as OTPAuth from "otpauth";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,7 +20,7 @@ class GenerateHOTP extends Operation {
|
|||||||
|
|
||||||
this.name = "Generate HOTP";
|
this.name = "Generate HOTP";
|
||||||
this.module = "Default";
|
this.module = "Default";
|
||||||
this.description = "The HMAC-based One-Time Password algorithm (HOTP) is an algorithm that computes a one-time password from a shared secret key and an incrementing counter. It has been adopted as Internet Engineering Task Force standard RFC 4226, is the cornerstone of Initiative For Open Authentication (OAUTH), and is used in a number of two-factor authentication systems.<br><br>Enter the secret as the input or leave it blank for a random secret to be generated.";
|
this.description = "The HMAC-based One-Time Password algorithm (HOTP) is an algorithm that computes a one-time password from a shared secret key and an incrementing counter. It has been adopted as Internet Engineering Task Force standard RFC 4226, is the cornerstone of Initiative For Open Authentication (OAUTH), and is used in a number of two-factor authentication systems.<br><br>Enter the secret as the input or leave it blank for a random secret to be generated. The secret must be a valid base32 string (characters A–Z and 2–7).";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/HMAC-based_One-time_Password_algorithm";
|
this.infoURL = "https://wikipedia.org/wiki/HMAC-based_One-time_Password_algorithm";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "ArrayBuffer";
|
||||||
this.outputType = "string";
|
this.outputType = "string";
|
||||||
@ -27,17 +28,23 @@ class GenerateHOTP extends Operation {
|
|||||||
{
|
{
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"value": ""
|
"value": "Account",
|
||||||
|
"allowEmpty": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Code length",
|
"name": "Code length",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"value": 6
|
"value": 6,
|
||||||
|
"min": 6,
|
||||||
|
"max": 8,
|
||||||
|
"integer": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Counter",
|
"name": "Counter",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"value": 0
|
"value": 0,
|
||||||
|
"min": 0,
|
||||||
|
"integer": true
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -47,7 +54,15 @@ class GenerateHOTP extends Operation {
|
|||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const secretStr = new TextDecoder("utf-8").decode(input).trim();
|
const secretStr = new TextDecoder("utf-8").decode(input).trim();
|
||||||
const secret = secretStr ? secretStr.toUpperCase().replace(/\s+/g, "") : "";
|
|
||||||
|
let secret;
|
||||||
|
try {
|
||||||
|
secret = secretStr ?
|
||||||
|
OTPAuth.Secret.fromBase32(secretStr.toUpperCase().replace(/\s+/g, "")) :
|
||||||
|
new OTPAuth.Secret();
|
||||||
|
} catch {
|
||||||
|
throw new OperationError("Invalid secret. The input must be a valid base32 string (characters A–Z and 2–7).");
|
||||||
|
}
|
||||||
|
|
||||||
const hotp = new OTPAuth.HOTP({
|
const hotp = new OTPAuth.HOTP({
|
||||||
issuer: "",
|
issuer: "",
|
||||||
@ -55,7 +70,7 @@ class GenerateHOTP extends Operation {
|
|||||||
algorithm: "SHA1",
|
algorithm: "SHA1",
|
||||||
digits: args[1],
|
digits: args[1],
|
||||||
counter: args[2],
|
counter: args[2],
|
||||||
secret: OTPAuth.Secret.fromBase32(secret)
|
secret
|
||||||
});
|
});
|
||||||
|
|
||||||
const uri = hotp.toString();
|
const uri = hotp.toString();
|
||||||
|
|||||||
@ -12,6 +12,14 @@ import { toBase64 } from "../lib/Base64.mjs";
|
|||||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
import { Jimp, JimpMime, ResizeStrategy, rgbaToInt } from "jimp";
|
import { Jimp, JimpMime, ResizeStrategy, rgbaToInt } from "jimp";
|
||||||
|
|
||||||
|
// arbitrary limits to prevent resource exhaustion
|
||||||
|
// scale factor of 64 is big enough to likely result in scaling in the display
|
||||||
|
// window anyway
|
||||||
|
// pixels per row is harder to come up with a figure that won't inconvenience
|
||||||
|
// someone. 2048 feels like a reasonable compromise
|
||||||
|
const MAX_PIXEL_SCALE_FACTOR = 64;
|
||||||
|
const MAX_PIXELS_PER_ROW = 2048;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate Image operation
|
* Generate Image operation
|
||||||
*/
|
*/
|
||||||
@ -40,11 +48,17 @@ class GenerateImage extends Operation {
|
|||||||
name: "Pixel Scale Factor",
|
name: "Pixel Scale Factor",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 8,
|
value: 8,
|
||||||
|
integer: true,
|
||||||
|
min: 1,
|
||||||
|
max: MAX_PIXEL_SCALE_FACTOR,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Pixels per row",
|
name: "Pixels per row",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 64,
|
value: 64,
|
||||||
|
integer: true,
|
||||||
|
min: 1,
|
||||||
|
max: MAX_PIXELS_PER_ROW,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -58,14 +72,6 @@ class GenerateImage extends Operation {
|
|||||||
const [mode, scale, width] = args;
|
const [mode, scale, width] = args;
|
||||||
input = new Uint8Array(input);
|
input = new Uint8Array(input);
|
||||||
|
|
||||||
if (scale <= 0) {
|
|
||||||
throw new OperationError("Pixel Scale Factor needs to be > 0");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (width <= 0) {
|
|
||||||
throw new OperationError("Pixels per Row needs to be > 0");
|
|
||||||
}
|
|
||||||
|
|
||||||
const bytePerPixelMap = {
|
const bytePerPixelMap = {
|
||||||
Greyscale: 1,
|
Greyscale: 1,
|
||||||
RG: 2,
|
RG: 2,
|
||||||
@ -74,6 +80,10 @@ class GenerateImage extends Operation {
|
|||||||
Bits: 1 / 8,
|
Bits: 1 / 8,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!Object.hasOwn(bytePerPixelMap, mode)) {
|
||||||
|
throw new OperationError(`Unsupported Mode: (${mode})`);
|
||||||
|
}
|
||||||
|
|
||||||
const bytesPerPixel = bytePerPixelMap[mode];
|
const bytesPerPixel = bytePerPixelMap[mode];
|
||||||
|
|
||||||
if (bytesPerPixel > 0 && input.length % bytesPerPixel !== 0) {
|
if (bytesPerPixel > 0 && input.length % bytesPerPixel !== 0) {
|
||||||
@ -163,8 +173,10 @@ class GenerateImage extends Operation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const imageBuffer = await image.getBuffer(JimpMime.png);
|
// see https://nodejs.org/docs/latest-v24.x/api/buffer.html#bufbyteoffset
|
||||||
return imageBuffer.buffer;
|
// for why we can't just return result.buffer
|
||||||
|
const result = await image.getBuffer(JimpMime.png);
|
||||||
|
return result.buffer.slice(result.byteOffset, result.byteOffset + result.byteLength);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(`Error generating image. (${err})`);
|
throw new OperationError(`Error generating image. (${err})`);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,10 @@ import Operation from "../Operation.mjs";
|
|||||||
import OperationError from "../errors/OperationError.mjs";
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
import { GenerateParagraphs, GenerateSentences, GenerateWords, GenerateBytes } from "../lib/LoremIpsum.mjs";
|
import { GenerateParagraphs, GenerateSentences, GenerateWords, GenerateBytes } from "../lib/LoremIpsum.mjs";
|
||||||
|
|
||||||
|
// arbitrary limits set to avoid DoS by requesting ridiculous amounts of data
|
||||||
|
const maxLoremWords = 100_000; // same limit also used for paragraphs/sentences
|
||||||
|
const maxLoremCharacters = 1_000_000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate Lorem Ipsum operation
|
* Generate Lorem Ipsum operation
|
||||||
*/
|
*/
|
||||||
@ -47,9 +51,7 @@ class GenerateLoremIpsum extends Operation {
|
|||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const [length, lengthType] = args;
|
const [length, lengthType] = args;
|
||||||
if (length < 1) {
|
checkLimits(lengthType, length);
|
||||||
throw new OperationError("Length must be greater than 0");
|
|
||||||
}
|
|
||||||
switch (lengthType) {
|
switch (lengthType) {
|
||||||
case "Paragraphs":
|
case "Paragraphs":
|
||||||
return GenerateParagraphs(length);
|
return GenerateParagraphs(length);
|
||||||
@ -68,3 +70,32 @@ class GenerateLoremIpsum extends Operation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default GenerateLoremIpsum;
|
export default GenerateLoremIpsum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check combined validity of lengthType and length arguments
|
||||||
|
* @param {string} lengthType
|
||||||
|
* @param {number} length
|
||||||
|
* @throws {OperationError}
|
||||||
|
*/
|
||||||
|
function checkLimits(lengthType, length) {
|
||||||
|
if (length < 1) {
|
||||||
|
throw new OperationError("Length must be greater than 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (lengthType) {
|
||||||
|
case "Paragraphs":
|
||||||
|
case "Sentences":
|
||||||
|
case "Words":
|
||||||
|
if (length > maxLoremWords) {
|
||||||
|
throw new OperationError("Length must be less than " + maxLoremWords);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Bytes":
|
||||||
|
if (length > maxLoremCharacters) {
|
||||||
|
throw new OperationError("Length must be less than " + maxLoremCharacters);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new OperationError("Invalid length type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Operation from "../Operation.mjs";
|
import Operation from "../Operation.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
import * as OTPAuth from "otpauth";
|
import * as OTPAuth from "otpauth";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -18,7 +19,7 @@ class GenerateTOTP extends Operation {
|
|||||||
super();
|
super();
|
||||||
this.name = "Generate TOTP";
|
this.name = "Generate TOTP";
|
||||||
this.module = "Default";
|
this.module = "Default";
|
||||||
this.description = "The Time-based One-Time Password algorithm (TOTP) is an algorithm that computes a one-time password from a shared secret key and the current time. It has been adopted as Internet Engineering Task Force standard RFC 6238, is the cornerstone of Initiative For Open Authentication (OAUTH), and is used in a number of two-factor authentication systems. A TOTP is an HOTP where the counter is the current time.<br><br>Enter the secret as the input or leave it blank for a random secret to be generated. T0 and T1 are in seconds.";
|
this.description = "The Time-based One-Time Password algorithm (TOTP) is an algorithm that computes a one-time password from a shared secret key and the current time. It has been adopted as Internet Engineering Task Force standard RFC 6238, is the cornerstone of Initiative For Open Authentication (OAUTH), and is used in a number of two-factor authentication systems. A TOTP is an HOTP where the counter is the current time.<br><br>Enter the secret as the input or leave it blank for a random secret to be generated. The secret must be a valid base32 string (characters A–Z and 2–7). T0 and T1 are in seconds.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Time-based_One-time_Password_algorithm";
|
this.infoURL = "https://wikipedia.org/wiki/Time-based_One-time_Password_algorithm";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "ArrayBuffer";
|
||||||
this.outputType = "string";
|
this.outputType = "string";
|
||||||
@ -26,22 +27,30 @@ class GenerateTOTP extends Operation {
|
|||||||
{
|
{
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"value": ""
|
"value": "Account",
|
||||||
|
"allowEmpty": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Code length",
|
"name": "Code length",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"value": 6
|
"value": 6,
|
||||||
|
"min": 6,
|
||||||
|
"max": 8,
|
||||||
|
"integer": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Epoch offset (T0)",
|
"name": "Epoch offset (T0)",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"value": 0
|
"value": 0,
|
||||||
|
"min": 0,
|
||||||
|
"integer": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Interval (T1)",
|
"name": "Interval (T1)",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"value": 30
|
"value": 30,
|
||||||
|
"min": 1,
|
||||||
|
"integer": true
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -51,7 +60,15 @@ class GenerateTOTP extends Operation {
|
|||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const secretStr = new TextDecoder("utf-8").decode(input).trim();
|
const secretStr = new TextDecoder("utf-8").decode(input).trim();
|
||||||
const secret = secretStr ? secretStr.toUpperCase().replace(/\s+/g, "") : "";
|
|
||||||
|
let secret;
|
||||||
|
try {
|
||||||
|
secret = secretStr ?
|
||||||
|
OTPAuth.Secret.fromBase32(secretStr.toUpperCase().replace(/\s+/g, "")) :
|
||||||
|
new OTPAuth.Secret();
|
||||||
|
} catch {
|
||||||
|
throw new OperationError("Invalid secret. The input must be a valid base32 string (characters A–Z and 2–7).");
|
||||||
|
}
|
||||||
|
|
||||||
const totp = new OTPAuth.TOTP({
|
const totp = new OTPAuth.TOTP({
|
||||||
issuer: "",
|
issuer: "",
|
||||||
@ -60,7 +77,7 @@ class GenerateTOTP extends Operation {
|
|||||||
digits: args[1],
|
digits: args[1],
|
||||||
period: args[3],
|
period: args[3],
|
||||||
epoch: args[2] * 1000, // Convert seconds to milliseconds
|
epoch: args[2] * 1000, // Convert seconds to milliseconds
|
||||||
secret: OTPAuth.Secret.fromBase32(secret)
|
secret
|
||||||
});
|
});
|
||||||
|
|
||||||
const uri = totp.toString();
|
const uri = totp.toString();
|
||||||
|
|||||||
@ -74,13 +74,11 @@ class Gzip extends Operation {
|
|||||||
}
|
}
|
||||||
if (comment.length) {
|
if (comment.length) {
|
||||||
options.flags.comment = true;
|
options.flags.comment = true;
|
||||||
|
options.flags.fcomment = true;
|
||||||
options.comment = comment;
|
options.comment = comment;
|
||||||
}
|
}
|
||||||
const gzipObj = new Zlib.Gzip(new Uint8Array(input), options);
|
const gzipObj = new Zlib.Gzip(new Uint8Array(input), options);
|
||||||
const compressed = new Uint8Array(gzipObj.compress());
|
const compressed = new Uint8Array(gzipObj.compress());
|
||||||
if (options.flags.comment && !(compressed[3] & 0x10)) {
|
|
||||||
compressed[3] |= 0x10;
|
|
||||||
}
|
|
||||||
return compressed.buffer;
|
return compressed.buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -51,6 +51,18 @@ class JsonataQuery extends Operation {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const expression = jsonata(query);
|
const expression = jsonata(query);
|
||||||
|
// Override built-in base64 functions which fail in Web Worker
|
||||||
|
// context where `window` is undefined. The jsonata library falls
|
||||||
|
// back to `global.Buffer` which also does not exist in workers.
|
||||||
|
// `atob`/`btoa` are available in both browser and worker scopes.
|
||||||
|
expression.registerFunction("base64decode", (str) => {
|
||||||
|
if (typeof str === "undefined") return undefined;
|
||||||
|
return atob(str);
|
||||||
|
}, "<s-:s>");
|
||||||
|
expression.registerFunction("base64encode", (str) => {
|
||||||
|
if (typeof str === "undefined") return undefined;
|
||||||
|
return btoa(str);
|
||||||
|
}, "<s-:s>");
|
||||||
result = await expression.evaluate(jsonObj);
|
result = await expression.evaluate(jsonObj);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new OperationError(
|
throw new OperationError(
|
||||||
|
|||||||
@ -87,7 +87,7 @@ class MIMEDecoding extends Operation {
|
|||||||
end = cur + j + "?=".length;
|
end = cur + j + "?=".length;
|
||||||
|
|
||||||
if (encoding.toLowerCase() === "b") {
|
if (encoding.toLowerCase() === "b") {
|
||||||
text = fromBase64(text);
|
text = fromBase64(text, undefined, "byteArray");
|
||||||
} else if (encoding.toLowerCase() === "q") {
|
} else if (encoding.toLowerCase() === "q") {
|
||||||
text = this.parseQEncodedWord(text);
|
text = this.parseQEncodedWord(text);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -20,7 +20,7 @@ class ParityBit extends Operation {
|
|||||||
|
|
||||||
this.name = "Parity Bit";
|
this.name = "Parity Bit";
|
||||||
this.module = "Default";
|
this.module = "Default";
|
||||||
this.description = "A parity bit, or check bit, is the simplest form of error detection. It is a bit which is added to a string of bits and represents if the number of 1's in the binary string is an even number or odd number.<br><br>If a delimiter is specified, the parity bit calculation will be performed on each 'block' of the input data, where the blocks are created by slicing the input at each occurence of the delimiter character";
|
this.description = "A parity bit, or check bit, is the simplest form of error detection. It is a bit which is added to a string of bits and represents if the number of 1's in the binary string is an even number or odd number.<br><br>If a delimiter is specified, the parity bit calculation will be performed on each 'block' of the input data, where the blocks are created by slicing the input at each occurrence of the delimiter character";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Parity_bit";
|
this.infoURL = "https://wikipedia.org/wiki/Parity_bit";
|
||||||
this.inputType = "string";
|
this.inputType = "string";
|
||||||
this.outputType = "string";
|
this.outputType = "string";
|
||||||
|
|||||||
@ -74,7 +74,7 @@ class ParseIPv4Header extends Operation {
|
|||||||
checksum = input[10] << 8 | input[11],
|
checksum = input[10] << 8 | input[11],
|
||||||
srcIP = input[12] << 24 | input[13] << 16 | input[14] << 8 | input[15],
|
srcIP = input[12] << 24 | input[13] << 16 | input[14] << 8 | input[15],
|
||||||
dstIP = input[16] << 24 | input[17] << 16 | input[18] << 8 | input[19],
|
dstIP = input[16] << 24 | input[17] << 16 | input[18] << 8 | input[19],
|
||||||
checksumHeader = input.slice(0, 10).concat([0, 0]).concat(input.slice(12, 20));
|
checksumHeader = [...input.slice(0, 10), 0, 0, ...input.slice(12, 20)];
|
||||||
let version = (input[0] >>> 4) & 0x0f,
|
let version = (input[0] >>> 4) & 0x0f,
|
||||||
options = [];
|
options = [];
|
||||||
|
|
||||||
|
|||||||
@ -33,7 +33,7 @@ class ParseURI extends Operation {
|
|||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const uri = url.parse(input, true);
|
const uri = url.parse(input, false);
|
||||||
|
|
||||||
let output = "";
|
let output = "";
|
||||||
|
|
||||||
@ -43,7 +43,20 @@ class ParseURI extends Operation {
|
|||||||
if (uri.port) output += "Port:\t\t" + uri.port + "\n";
|
if (uri.port) output += "Port:\t\t" + uri.port + "\n";
|
||||||
if (uri.pathname) output += "Path name:\t" + uri.pathname + "\n";
|
if (uri.pathname) output += "Path name:\t" + uri.pathname + "\n";
|
||||||
if (uri.query) {
|
if (uri.query) {
|
||||||
const keys = Object.keys(uri.query);
|
const queryObj = Object.create(null);
|
||||||
|
for (const [key, value] of new URLSearchParams(uri.query)) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(queryObj, key)) {
|
||||||
|
if (Array.isArray(queryObj[key])) {
|
||||||
|
queryObj[key].push(value);
|
||||||
|
} else {
|
||||||
|
queryObj[key] = [queryObj[key], value];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
queryObj[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys = Object.keys(queryObj);
|
||||||
let padding = 0;
|
let padding = 0;
|
||||||
|
|
||||||
keys.forEach(k => {
|
keys.forEach(k => {
|
||||||
@ -51,10 +64,10 @@ class ParseURI extends Operation {
|
|||||||
});
|
});
|
||||||
|
|
||||||
output += "Arguments:\n";
|
output += "Arguments:\n";
|
||||||
for (const key in uri.query) {
|
for (const key in queryObj) {
|
||||||
output += "\t" + key.padEnd(padding, " ");
|
output += "\t" + key.padEnd(padding, " ");
|
||||||
if (uri.query[key].length) {
|
if (queryObj[key].length) {
|
||||||
output += " = " + uri.query[key] + "\n";
|
output += " = " + queryObj[key] + "\n";
|
||||||
} else {
|
} else {
|
||||||
output += "\n";
|
output += "\n";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Operation from "../Operation.mjs";
|
import Operation from "../Operation.mjs";
|
||||||
import UAParser from "ua-parser-js";
|
import { UAParser } from "ua-parser-js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse User Agent operation
|
* Parse User Agent operation
|
||||||
|
|||||||
@ -31,7 +31,8 @@ class PseudoRandomNumberGenerator extends Operation {
|
|||||||
{
|
{
|
||||||
"name": "Number of bytes",
|
"name": "Number of bytes",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"value": 32
|
"value": 32,
|
||||||
|
"min": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Output as",
|
"name": "Output as",
|
||||||
|
|||||||
83
src/core/operations/ROR13.mjs
Normal file
83
src/core/operations/ROR13.mjs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* ROR13 Hash operation (Windows API hashing convention)
|
||||||
|
* @author fufu_btw
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a ROR13 hash used for API name hashing techniques.
|
||||||
|
*/
|
||||||
|
class ROR13 extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "ROR13";
|
||||||
|
this.module = "Default";
|
||||||
|
this.description = "Computes a ROR13 hash used in API hashing techniques.";
|
||||||
|
this.infoURL = "";
|
||||||
|
this.inputType = "byteArray";
|
||||||
|
this.outputType = "string";
|
||||||
|
|
||||||
|
this.args = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotate right (32-bit)
|
||||||
|
*
|
||||||
|
* @param {number} value - input value
|
||||||
|
* @param {number} bits - rotation bits
|
||||||
|
* @returns {number} rotated value
|
||||||
|
*/
|
||||||
|
ror(value, bits) {
|
||||||
|
return ((value >>> bits) | (value << (32 - bits))) >>> 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute ROR13 hash
|
||||||
|
*
|
||||||
|
* @param {byteArray} input - input bytes
|
||||||
|
* @param {Object[]} args - operation arguments
|
||||||
|
* @returns {string} hex hash
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
let hash = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < input.length; i++) {
|
||||||
|
const chr = input[i] & 0xFF;
|
||||||
|
hash = this.ror(hash, 13);
|
||||||
|
hash = (hash + chr) >>> 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "0x" + hash.toString(16).padStart(8, "0").toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highlight input
|
||||||
|
*
|
||||||
|
* @param {Object[]} pos
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {Object[]}
|
||||||
|
*/
|
||||||
|
highlight(pos, args) {
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse highlight
|
||||||
|
*
|
||||||
|
* @param {Object[]} pos
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {Object[]}
|
||||||
|
*/
|
||||||
|
highlightReverse(pos, args) {
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ROR13;
|
||||||
100
src/core/operations/RenderPDF.mjs
Normal file
100
src/core/operations/RenderPDF.mjs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
/**
|
||||||
|
* @author Shailendra [singhshailendra.in]
|
||||||
|
* @copyright Crown Copyright 2017
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { fromBase64, toBase64 } from "../lib/Base64.mjs";
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render PDF operation
|
||||||
|
*/
|
||||||
|
class RenderPDF extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RenderPDF constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Render PDF";
|
||||||
|
this.module = "File";
|
||||||
|
this.description = "Displays the input as a PDF preview. Supports Raw and Base64 input formats.";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "byteArray";
|
||||||
|
this.presentType = "html";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Input format",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["Base64", "Raw"],
|
||||||
|
}
|
||||||
|
];
|
||||||
|
this.checks = [
|
||||||
|
{
|
||||||
|
pattern: "^%PDF-",
|
||||||
|
flags: "",
|
||||||
|
args: ["Raw"],
|
||||||
|
useful: true,
|
||||||
|
output: {
|
||||||
|
mime: "application/pdf"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {byteArray}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const inputFormat = args[0];
|
||||||
|
|
||||||
|
if (!input.length) return [];
|
||||||
|
|
||||||
|
// Convert input to raw bytes
|
||||||
|
switch (inputFormat) {
|
||||||
|
case "Base64":
|
||||||
|
input = fromBase64(input, undefined, "byteArray");
|
||||||
|
break;
|
||||||
|
case "Raw":
|
||||||
|
default:
|
||||||
|
input = Utils.strToByteArray(input);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check PDF signature
|
||||||
|
if (
|
||||||
|
input[0] !== 0x25 || // %
|
||||||
|
input[1] !== 0x50 || // P
|
||||||
|
input[2] !== 0x44 || // D
|
||||||
|
input[3] !== 0x46 // F
|
||||||
|
) {
|
||||||
|
throw new OperationError("Input does not appear to be a PDF file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the PDF using HTML for web apps.
|
||||||
|
*
|
||||||
|
* @param {byteArray} data
|
||||||
|
* @returns {html}
|
||||||
|
*/
|
||||||
|
async present(data) {
|
||||||
|
if (!data.length) return "";
|
||||||
|
|
||||||
|
const base64 = toBase64(data);
|
||||||
|
const dataURI = "data:application/pdf;base64," + base64;
|
||||||
|
|
||||||
|
return `<iframe src="${dataURI}" style="width:100%;height:100%;border:1px solid #ccc;"></iframe>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RenderPDF;
|
||||||
@ -20,7 +20,7 @@ class SHA2 extends Operation {
|
|||||||
|
|
||||||
this.name = "SHA2";
|
this.name = "SHA2";
|
||||||
this.module = "Crypto";
|
this.module = "Crypto";
|
||||||
this.description = "The SHA-2 (Secure Hash Algorithm 2) hash functions were designed by the NSA. SHA-2 includes significant changes from its predecessor, SHA-1. The SHA-2 family consists of hash functions with digests (hash values) that are 224, 256, 384 or 512 bits: SHA224, SHA256, SHA384, SHA512.<br><br><ul><li>SHA-512 operates on 64-bit words.</li><li>SHA-256 operates on 32-bit words.</li><li>SHA-384 is largely identical to SHA-512 but is truncated to 384 bytes.</li><li>SHA-224 is largely identical to SHA-256 but is truncated to 224 bytes.</li><li>SHA-512/224 and SHA-512/256 are truncated versions of SHA-512, but the initial values are generated using the method described in Federal Information Processing Standards (FIPS) PUB 180-4.</li></ul> The message digest algorithm for SHA256 variants consists, by default, of 64 rounds, and for SHA512 variants, it is, by default, 160.";
|
this.description = "The SHA-2 (Secure Hash Algorithm 2) hash functions were designed by the NSA. SHA-2 includes significant changes from its predecessor, SHA-1. The SHA-2 family consists of hash functions with digests (hash values) that are 224, 256, 384 or 512 bits: SHA224, SHA256, SHA384, SHA512.<br><br><ul><li>SHA-512 operates on 64-bit words.</li><li>SHA-256 operates on 32-bit words.</li><li>SHA-384 is largely identical to SHA-512 but is truncated to 384 bits.</li><li>SHA-224 is largely identical to SHA-256 but is truncated to 224 bits.</li><li>SHA-512/224 and SHA-512/256 are truncated versions of SHA-512, but the initial values are generated using the method described in Federal Information Processing Standards (FIPS) PUB 180-4.</li></ul> The message digest algorithm for SHA256 variants consists, by default, of 64 rounds, and for SHA512 variants, it is, by default, 160.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/SHA-2";
|
this.infoURL = "https://wikipedia.org/wiki/SHA-2";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "ArrayBuffer";
|
||||||
this.outputType = "string";
|
this.outputType = "string";
|
||||||
|
|||||||
@ -43,7 +43,7 @@ class SM4Encrypt 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",
|
||||||
|
|||||||
@ -75,9 +75,16 @@ class SetDifference extends Operation {
|
|||||||
* @returns {Object[]}
|
* @returns {Object[]}
|
||||||
*/
|
*/
|
||||||
runSetDifference(a, b) {
|
runSetDifference(a, b) {
|
||||||
|
const excluded = new Set(b);
|
||||||
|
const seen = new Set();
|
||||||
|
|
||||||
return a
|
return a
|
||||||
.filter((item) => {
|
.filter((item) => {
|
||||||
return b.indexOf(item) === -1;
|
if (excluded.has(item) || seen.has(item)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
seen.add(item);
|
||||||
|
return true;
|
||||||
})
|
})
|
||||||
.join(this.itemDelimiter);
|
.join(this.itemDelimiter);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -75,9 +75,16 @@ class SetIntersection extends Operation {
|
|||||||
* @returns {Object[]}
|
* @returns {Object[]}
|
||||||
*/
|
*/
|
||||||
runIntersect(a, b) {
|
runIntersect(a, b) {
|
||||||
|
const included = new Set(b);
|
||||||
|
const seen = new Set();
|
||||||
|
|
||||||
return a
|
return a
|
||||||
.filter((item) => {
|
.filter((item) => {
|
||||||
return b.indexOf(item) > -1;
|
if (!included.has(item) || seen.has(item)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
seen.add(item);
|
||||||
|
return true;
|
||||||
})
|
})
|
||||||
.join(this.itemDelimiter);
|
.join(this.itemDelimiter);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,7 +36,8 @@ class ShowOnMap extends Operation {
|
|||||||
{
|
{
|
||||||
name: "Input Format",
|
name: "Input Format",
|
||||||
type: "option",
|
type: "option",
|
||||||
value: ["Auto"].concat(FORMATS)
|
value: ["Auto"].concat(FORMATS),
|
||||||
|
allowEmpty: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Input Delimiter",
|
name: "Input Delimiter",
|
||||||
@ -49,7 +50,8 @@ class ShowOnMap extends Operation {
|
|||||||
"Comma",
|
"Comma",
|
||||||
"Semi-colon",
|
"Semi-colon",
|
||||||
"Colon"
|
"Colon"
|
||||||
]
|
],
|
||||||
|
allowEmpty: false
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -71,6 +73,16 @@ class ShowOnMap extends Operation {
|
|||||||
}
|
}
|
||||||
latLong = latLong.replace(/[,]$/, "");
|
latLong = latLong.replace(/[,]$/, "");
|
||||||
latLong = latLong.replace(/°/g, "");
|
latLong = latLong.replace(/°/g, "");
|
||||||
|
|
||||||
|
// The map requires a latitude and longitude pair. If the conversion only produced a
|
||||||
|
// single value (e.g. because the chosen input delimiter didn't match the input), bail
|
||||||
|
// out with a helpful message rather than passing it on to the map, which would throw an
|
||||||
|
// uncaught TypeError in the browser.
|
||||||
|
const coords = latLong.split(",").map(v => v.trim());
|
||||||
|
if (coords.length !== 2 || coords.some(v => v === "" || isNaN(Number(v)))) {
|
||||||
|
throw new OperationError(`Could not show coordinates '${latLong}' on the map. Expected a latitude and longitude pair - check that the input format and delimiter are correct.`);
|
||||||
|
}
|
||||||
|
|
||||||
return latLong;
|
return latLong;
|
||||||
}
|
}
|
||||||
return input;
|
return input;
|
||||||
|
|||||||
@ -28,7 +28,10 @@ class ToBase extends Operation {
|
|||||||
{
|
{
|
||||||
"name": "Radix",
|
"name": "Radix",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"value": 36
|
"value": 36,
|
||||||
|
"min": 2,
|
||||||
|
"max": 36,
|
||||||
|
"integer": true,
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -43,9 +46,6 @@ class ToBase extends Operation {
|
|||||||
throw new OperationError("Error: Input must be a number");
|
throw new OperationError("Error: Input must be a number");
|
||||||
}
|
}
|
||||||
const radix = args[0];
|
const radix = args[0];
|
||||||
if (radix < 2 || radix > 36) {
|
|
||||||
throw new OperationError("Error: Radix argument must be between 2 and 36");
|
|
||||||
}
|
|
||||||
return input.toString(radix);
|
return input.toString(radix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -43,7 +43,14 @@ class ToBase32 extends Operation {
|
|||||||
if (!input) return "";
|
if (!input) return "";
|
||||||
input = new Uint8Array(input);
|
input = new Uint8Array(input);
|
||||||
|
|
||||||
const alphabet = args[0] ? Utils.expandAlphRange(args[0]).join("") : "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=";
|
const alphabet = args[0] ?
|
||||||
|
Utils.expandAlphRange(args[0]).join("") :
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=";
|
||||||
|
|
||||||
|
// Unicode-safe alphabet handling
|
||||||
|
// Supports BMP + non-BMP characters (emoji, Mahjong tiles, etc.)
|
||||||
|
const alphabetChars = Array.from(alphabet);
|
||||||
|
|
||||||
let output = "",
|
let output = "",
|
||||||
chr1, chr2, chr3, chr4, chr5,
|
chr1, chr2, chr3, chr4, chr5,
|
||||||
enc1, enc2, enc3, enc4, enc5, enc6, enc7, enc8,
|
enc1, enc2, enc3, enc4, enc5, enc6, enc7, enc8,
|
||||||
@ -74,10 +81,19 @@ class ToBase32 extends Operation {
|
|||||||
enc8 = 32;
|
enc8 = 32;
|
||||||
}
|
}
|
||||||
|
|
||||||
output += alphabet.charAt(enc1) + alphabet.charAt(enc2) + alphabet.charAt(enc3) +
|
// Preserve original charAt() behavior:
|
||||||
alphabet.charAt(enc4) + alphabet.charAt(enc5) + alphabet.charAt(enc6) +
|
// out-of-range indexes return ""
|
||||||
alphabet.charAt(enc7) + alphabet.charAt(enc8);
|
output +=
|
||||||
|
(alphabetChars[enc1] || "") +
|
||||||
|
(alphabetChars[enc2] || "") +
|
||||||
|
(alphabetChars[enc3] || "") +
|
||||||
|
(alphabetChars[enc4] || "") +
|
||||||
|
(alphabetChars[enc5] || "") +
|
||||||
|
(alphabetChars[enc6] || "") +
|
||||||
|
(alphabetChars[enc7] || "") +
|
||||||
|
(alphabetChars[enc8] || "");
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -35,7 +35,10 @@ class ToBinary extends Operation {
|
|||||||
{
|
{
|
||||||
"name": "Byte Length",
|
"name": "Byte Length",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"value": 8
|
"value": 8,
|
||||||
|
"min": 1,
|
||||||
|
"max": 256, // arbitrary - significantly larger than word size for any known machine ("640k ought to be enough for anybody")
|
||||||
|
"integer": true
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import Operation from "../Operation.mjs";
|
|||||||
import Utils from "../Utils.mjs";
|
import Utils from "../Utils.mjs";
|
||||||
import OperationError from "../errors/OperationError.mjs";
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
|
||||||
|
const MAX_WIDTH = 65536;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To Hexdump operation
|
* To Hexdump operation
|
||||||
*/
|
*/
|
||||||
@ -30,7 +32,8 @@ class ToHexdump extends Operation {
|
|||||||
"name": "Width",
|
"name": "Width",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"value": 16,
|
"value": 16,
|
||||||
"min": 1
|
"min": 1,
|
||||||
|
"max": MAX_WIDTH
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Upper case hex",
|
"name": "Upper case hex",
|
||||||
@ -63,6 +66,9 @@ class ToHexdump extends Operation {
|
|||||||
if (length < 1 || Math.round(length) !== length)
|
if (length < 1 || Math.round(length) !== length)
|
||||||
throw new OperationError("Width must be a positive integer");
|
throw new OperationError("Width must be a positive integer");
|
||||||
|
|
||||||
|
if (length > MAX_WIDTH)
|
||||||
|
throw new OperationError(`Width must be no more than ${MAX_WIDTH}`);
|
||||||
|
|
||||||
const lines = [];
|
const lines = [];
|
||||||
for (let i = 0; i < data.length; i += length) {
|
for (let i = 0; i < data.length; i += length) {
|
||||||
let lineNo = Utils.hex(i, 8);
|
let lineNo = Utils.hex(i, 8);
|
||||||
|
|||||||
@ -21,7 +21,7 @@ class URLEncode extends Operation {
|
|||||||
this.module = "URL";
|
this.module = "URL";
|
||||||
this.description = "Encodes problematic characters into percent-encoding, a format supported by URIs/URLs.<br><br>e.g. <code>=</code> becomes <code>%3d</code>";
|
this.description = "Encodes problematic characters into percent-encoding, a format supported by URIs/URLs.<br><br>e.g. <code>=</code> becomes <code>%3d</code>";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Percent-encoding";
|
this.infoURL = "https://wikipedia.org/wiki/Percent-encoding";
|
||||||
this.inputType = "string";
|
this.inputType = "byteArray";
|
||||||
this.outputType = "string";
|
this.outputType = "string";
|
||||||
this.args = [
|
this.args = [
|
||||||
{
|
{
|
||||||
@ -33,34 +33,38 @@ class URLEncode extends Operation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} input
|
* @param {byteArray} input
|
||||||
* @param {Object[]} args
|
* @param {Object[]} args
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const encodeAll = args[0];
|
const encodeAll = args[0];
|
||||||
return encodeAll ? this.encodeAllChars(input) : encodeURI(input);
|
return this.encodeBytes(input, encodeAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode characters in URL outside of encodeURI() function spec
|
* Encode bytes in URL using percent encoding.
|
||||||
*
|
*
|
||||||
* @param {string} str
|
* @param {byteArray} bytes
|
||||||
|
* @param {boolean} encodeAll
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
encodeAllChars (str) {
|
encodeBytes(bytes, encodeAll) {
|
||||||
// TODO Do this programmatically
|
const safeChars = encodeAll ?
|
||||||
return encodeURIComponent(str)
|
/^[A-Za-z0-9]$/ :
|
||||||
.replace(/!/g, "%21")
|
/^[A-Za-z0-9:/?#[\]@!$&'()*+,;=%]$/;
|
||||||
.replace(/#/g, "%23")
|
|
||||||
.replace(/'/g, "%27")
|
let output = "";
|
||||||
.replace(/\(/g, "%28")
|
|
||||||
.replace(/\)/g, "%29")
|
for (const byte of bytes) {
|
||||||
.replace(/\*/g, "%2A")
|
const char = String.fromCharCode(byte);
|
||||||
.replace(/-/g, "%2D")
|
|
||||||
.replace(/\./g, "%2E")
|
output += safeChars.test(char) ?
|
||||||
.replace(/_/g, "%5F")
|
char :
|
||||||
.replace(/~/g, "%7E");
|
"%" + byte.toString(16).toUpperCase().padStart(2, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,7 +56,8 @@ class UnescapeUnicodeCharacters extends Operation {
|
|||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const prefix = prefixToRegex[args[0]],
|
const prefix = prefixToRegex[args[0]],
|
||||||
regex = new RegExp(prefix+"([a-f\\d]{4})", "ig");
|
quantifier = args[0] === "U+" ? "{4,6}" : "{4}",
|
||||||
|
regex = new RegExp(prefix+"([a-f\\d]"+quantifier+")", "ig");
|
||||||
let output = "",
|
let output = "",
|
||||||
m,
|
m,
|
||||||
i = 0;
|
i = 0;
|
||||||
|
|||||||
@ -52,9 +52,15 @@ class ViewBitPlane extends Operation {
|
|||||||
if (!isImage(input))
|
if (!isImage(input))
|
||||||
throw new OperationError("Please enter a valid image file.");
|
throw new OperationError("Please enter a valid image file.");
|
||||||
|
|
||||||
const [colour, bit] = args,
|
const [colour, bit] = args;
|
||||||
parsedImage = await Jimp.read(input),
|
let parsedImage;
|
||||||
width = parsedImage.bitmap.width,
|
try {
|
||||||
|
parsedImage = await Jimp.read(input);
|
||||||
|
} catch (err) {
|
||||||
|
throw new OperationError(`Error loading image. (${err})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = parsedImage.bitmap.width,
|
||||||
height = parsedImage.bitmap.height,
|
height = parsedImage.bitmap.height,
|
||||||
colourIndex = COLOUR_OPTIONS.indexOf(colour),
|
colourIndex = COLOUR_OPTIONS.indexOf(colour),
|
||||||
bitIndex = 7 - bit;
|
bitIndex = 7 - bit;
|
||||||
|
|||||||
@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
import Operation from "../Operation.mjs";
|
import Operation from "../Operation.mjs";
|
||||||
|
|
||||||
|
const MAX_LINE_WIDTH = 65536;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrap operation
|
* Wrap operation
|
||||||
*/
|
*/
|
||||||
@ -27,6 +29,9 @@ class Wrap extends Operation {
|
|||||||
"name": "Line Width",
|
"name": "Line Width",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"value": 64,
|
"value": 64,
|
||||||
|
"min": 1,
|
||||||
|
"max": MAX_LINE_WIDTH,
|
||||||
|
"integer": true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,7 +31,10 @@ class XORBruteForce extends Operation {
|
|||||||
{
|
{
|
||||||
"name": "Key length",
|
"name": "Key length",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"value": 1
|
"value": 1,
|
||||||
|
"min": 1,
|
||||||
|
"max": 2,
|
||||||
|
"integer": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Sample length",
|
"name": "Sample length",
|
||||||
|
|||||||
@ -7,12 +7,12 @@
|
|||||||
import Operation from "../Operation.mjs";
|
import Operation from "../Operation.mjs";
|
||||||
import Utils from "../Utils.mjs";
|
import Utils from "../Utils.mjs";
|
||||||
import { toHex } from "../lib/Hex.mjs";
|
import { toHex } from "../lib/Hex.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* XOR Checksum operation
|
* XOR Checksum operation
|
||||||
*/
|
*/
|
||||||
class XORChecksum extends Operation {
|
class XORChecksum extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* XORChecksum constructor
|
* XORChecksum constructor
|
||||||
*/
|
*/
|
||||||
@ -21,7 +21,8 @@ class XORChecksum extends Operation {
|
|||||||
|
|
||||||
this.name = "XOR Checksum";
|
this.name = "XOR Checksum";
|
||||||
this.module = "Crypto";
|
this.module = "Crypto";
|
||||||
this.description = "XOR Checksum splits the input into blocks of a configurable size and performs the XOR operation on these blocks.";
|
this.description =
|
||||||
|
"XOR Checksum splits the input into blocks of a configurable size and performs the XOR operation on these blocks.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/XOR";
|
this.infoURL = "https://wikipedia.org/wiki/XOR";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "ArrayBuffer";
|
||||||
this.outputType = "string";
|
this.outputType = "string";
|
||||||
@ -29,7 +30,7 @@ class XORChecksum extends Operation {
|
|||||||
{
|
{
|
||||||
name: "Blocksize",
|
name: "Blocksize",
|
||||||
type: "number",
|
type: "number",
|
||||||
value: 4
|
value: 4,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -41,6 +42,12 @@ class XORChecksum extends Operation {
|
|||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const blocksize = args[0];
|
const blocksize = args[0];
|
||||||
|
|
||||||
|
|
||||||
|
if (!Number.isInteger(blocksize) || blocksize <= 0) {
|
||||||
|
throw new OperationError("Blocksize must be a positive integer.");
|
||||||
|
}
|
||||||
|
|
||||||
input = new Uint8Array(input);
|
input = new Uint8Array(input);
|
||||||
|
|
||||||
const res = Array(blocksize);
|
const res = Array(blocksize);
|
||||||
|
|||||||
162
src/core/vendor/ascon.mjs
vendored
Normal file
162
src/core/vendor/ascon.mjs
vendored
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
/**
|
||||||
|
* Ascon MAC implementation following NIST SP 800-232
|
||||||
|
* Vendor file for CyberChef
|
||||||
|
*
|
||||||
|
* @author Medjedtxm
|
||||||
|
* @copyright Crown Copyright 2025
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NIST SP 800-232 compliant Ascon-Mac implementation
|
||||||
|
* Uses little-endian byte ordering as per NIST specification
|
||||||
|
*/
|
||||||
|
class AsconMac {
|
||||||
|
// NIST SP 800-232 constants
|
||||||
|
static ASCON_MAC_IV = 0x0010200080cc0005n;
|
||||||
|
static ASCON_PRF_IN_RATE = 32; // 4 * 8 bytes
|
||||||
|
static ASCON_PRF_OUT_RATE = 16; // 2 * 8 bytes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute Ascon-Mac tag
|
||||||
|
* @param {Uint8Array} key - 16-byte key
|
||||||
|
* @param {Uint8Array} message - Message to authenticate
|
||||||
|
* @param {number} tagLength - Output tag length (default 16)
|
||||||
|
* @returns {Uint8Array} - MAC tag
|
||||||
|
*/
|
||||||
|
static mac(key, message, tagLength = 16) {
|
||||||
|
if (key.length !== 16) {
|
||||||
|
throw new Error(`Invalid key length: ${key.length} bytes. Ascon-Mac requires exactly 16 bytes.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialise state
|
||||||
|
const state = new BigUint64Array(5);
|
||||||
|
|
||||||
|
// Load key as two 64-bit words (little-endian per NIST SP 800-232)
|
||||||
|
const K0 = AsconMac.loadBytes(key, 0, 8);
|
||||||
|
const K1 = AsconMac.loadBytes(key, 8, 8);
|
||||||
|
|
||||||
|
// Set initial value per NIST SP 800-232
|
||||||
|
state[0] = AsconMac.ASCON_MAC_IV;
|
||||||
|
state[1] = K0;
|
||||||
|
state[2] = K1;
|
||||||
|
state[3] = 0n;
|
||||||
|
state[4] = 0n;
|
||||||
|
|
||||||
|
// Initial permutation P12
|
||||||
|
AsconMac.permutation(state, 12);
|
||||||
|
|
||||||
|
// Absorb message in 8-byte chunks, cycling through state[0..3]
|
||||||
|
let pos = 0;
|
||||||
|
let wordIdx = 0;
|
||||||
|
|
||||||
|
while (pos + 8 <= message.length) {
|
||||||
|
state[wordIdx] ^= AsconMac.loadBytes(message, pos, 8);
|
||||||
|
wordIdx++;
|
||||||
|
if (wordIdx === 4) {
|
||||||
|
wordIdx = 0;
|
||||||
|
AsconMac.permutation(state, 12);
|
||||||
|
}
|
||||||
|
pos += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Absorb final partial block with padding
|
||||||
|
const remaining = message.length - pos;
|
||||||
|
if (remaining > 0) {
|
||||||
|
state[wordIdx] ^= AsconMac.loadBytes(message, pos, remaining);
|
||||||
|
}
|
||||||
|
// PAD(remaining) = 0x01 << (8 * remaining)
|
||||||
|
state[wordIdx] ^= 0x01n << BigInt(8 * remaining);
|
||||||
|
|
||||||
|
// Domain separation: DSEP() = 0x80 << 56 = 0x8000000000000000
|
||||||
|
state[4] ^= 0x8000000000000000n;
|
||||||
|
|
||||||
|
// Finalisation permutation P12
|
||||||
|
AsconMac.permutation(state, 12);
|
||||||
|
|
||||||
|
// Squeeze output
|
||||||
|
const tag = new Uint8Array(tagLength);
|
||||||
|
let outPos = 0;
|
||||||
|
wordIdx = 0;
|
||||||
|
|
||||||
|
while (outPos < tagLength) {
|
||||||
|
const toCopy = Math.min(8, tagLength - outPos);
|
||||||
|
AsconMac.storeBytes(tag, outPos, state[wordIdx], toCopy);
|
||||||
|
outPos += toCopy;
|
||||||
|
wordIdx++;
|
||||||
|
if (wordIdx === 2 && outPos < tagLength) {
|
||||||
|
wordIdx = 0;
|
||||||
|
AsconMac.permutation(state, 12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load n bytes as little-endian 64-bit integer (NIST SP 800-232 byte order)
|
||||||
|
* LOADBYTES: bytes[i] goes to position i (byte 0 = LSB)
|
||||||
|
*/
|
||||||
|
static loadBytes(arr, offset, n) {
|
||||||
|
let result = 0n;
|
||||||
|
for (let i = 0; i < n && offset + i < arr.length; i++) {
|
||||||
|
result |= BigInt(arr[offset + i]) << BigInt(i * 8);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store n bytes from 64-bit integer in little-endian order
|
||||||
|
* STOREBYTES: position i goes to bytes[i] (LSB = byte 0)
|
||||||
|
*/
|
||||||
|
static storeBytes(arr, offset, val, n) {
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
arr[offset + i] = Number((val >> BigInt(i * 8)) & 0xFFn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ascon permutation
|
||||||
|
*/
|
||||||
|
static permutation(state, rounds) {
|
||||||
|
for (let r = 12 - rounds; r < 12; r++) {
|
||||||
|
// Add round constant
|
||||||
|
state[2] ^= BigInt(0xf0 - r * 0x10 + r);
|
||||||
|
|
||||||
|
// Substitution layer
|
||||||
|
state[0] ^= state[4];
|
||||||
|
state[4] ^= state[3];
|
||||||
|
state[2] ^= state[1];
|
||||||
|
|
||||||
|
const t0 = state[0] ^ (~state[1] & state[2]);
|
||||||
|
const t1 = state[1] ^ (~state[2] & state[3]);
|
||||||
|
const t2 = state[2] ^ (~state[3] & state[4]);
|
||||||
|
const t3 = state[3] ^ (~state[4] & state[0]);
|
||||||
|
const t4 = state[4] ^ (~state[0] & state[1]);
|
||||||
|
|
||||||
|
state[0] = t0 ^ t4;
|
||||||
|
state[1] = t1 ^ t0;
|
||||||
|
state[2] = ~t2;
|
||||||
|
state[3] = t3 ^ t2;
|
||||||
|
state[4] = t4;
|
||||||
|
|
||||||
|
// Linear diffusion layer
|
||||||
|
state[0] ^= AsconMac.rotr64(state[0], 19n) ^ AsconMac.rotr64(state[0], 28n);
|
||||||
|
state[1] ^= AsconMac.rotr64(state[1], 61n) ^ AsconMac.rotr64(state[1], 39n);
|
||||||
|
state[2] ^= AsconMac.rotr64(state[2], 1n) ^ AsconMac.rotr64(state[2], 6n);
|
||||||
|
state[3] ^= AsconMac.rotr64(state[3], 10n) ^ AsconMac.rotr64(state[3], 17n);
|
||||||
|
state[4] ^= AsconMac.rotr64(state[4], 7n) ^ AsconMac.rotr64(state[4], 41n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 64-bit rotate right
|
||||||
|
*/
|
||||||
|
static rotr64(val, n) {
|
||||||
|
const mask = 0xFFFFFFFFFFFFFFFFn;
|
||||||
|
val = val & mask;
|
||||||
|
return ((val >> n) | (val << (64n - n))) & mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AsconMac;
|
||||||
@ -74,7 +74,7 @@ function transformArgs(opArgsList, newArgs) {
|
|||||||
return opArgs.map((arg) => {
|
return opArgs.map((arg) => {
|
||||||
if (arg.type === "option") {
|
if (arg.type === "option") {
|
||||||
// pick default option if not already chosen
|
// pick default option if not already chosen
|
||||||
return typeof arg.value === "string" ? arg.value : arg.value[arg.defaultIndex ?? 0];
|
return !Array.isArray(arg.value) ? arg.value : arg.value[arg.defaultIndex ?? 0];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg.type === "editableOption") {
|
if (arg.type === "editableOption") {
|
||||||
@ -193,6 +193,8 @@ export function _wrap(OpClass) {
|
|||||||
wrapped = async (input, args=null) => {
|
wrapped = async (input, args=null) => {
|
||||||
const {transformedInput, transformedArgs} = prepareOp(opInstance, input, args);
|
const {transformedInput, transformedArgs} = prepareOp(opInstance, input, args);
|
||||||
|
|
||||||
|
opInstance.validateIngredients(transformedArgs);
|
||||||
|
|
||||||
// SPECIAL CASE for Magic. Other flowControl operations will
|
// SPECIAL CASE for Magic. Other flowControl operations will
|
||||||
// not work because the opList is not passed in.
|
// not work because the opList is not passed in.
|
||||||
if (isFlowControl) {
|
if (isFlowControl) {
|
||||||
@ -229,6 +231,7 @@ export function _wrap(OpClass) {
|
|||||||
*/
|
*/
|
||||||
wrapped = (input, args=null) => {
|
wrapped = (input, args=null) => {
|
||||||
const {transformedInput, transformedArgs} = prepareOp(opInstance, input, args);
|
const {transformedInput, transformedArgs} = prepareOp(opInstance, input, args);
|
||||||
|
opInstance.validateIngredients(transformedArgs);
|
||||||
const result = opInstance.run(transformedInput, transformedArgs);
|
const result = opInstance.run(transformedInput, transformedArgs);
|
||||||
return new NodeDish({
|
return new NodeDish({
|
||||||
value: result,
|
value: result,
|
||||||
|
|||||||
@ -56,9 +56,10 @@ class HTMLOperation {
|
|||||||
|
|
||||||
if (this.description) {
|
if (this.description) {
|
||||||
const infoLink = this.infoURL ? `<hr>${titleFromWikiLink(this.infoURL)}` : "";
|
const infoLink = this.infoURL ? `<hr>${titleFromWikiLink(this.infoURL)}` : "";
|
||||||
|
const content = Utils.escapeHtml(this.description + infoLink);
|
||||||
|
|
||||||
html += ` data-container='body' data-toggle='popover' data-placement='right'
|
html += ` data-container='body' data-toggle='popover' data-placement='right'
|
||||||
data-content="${this.description}${infoLink}" data-html='true' data-trigger='hover'
|
data-content="${content}" data-html='true' data-trigger='hover'
|
||||||
data-boundary='viewport' role='button'`;
|
data-boundary='viewport' role='button'`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,14 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
user-select: auto;
|
user-select: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#output-html > img {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
#output-text.html-output .cm-line .cm-widgetBuffer,
|
#output-text.html-output .cm-line .cm-widgetBuffer,
|
||||||
#output-text.html-output .cm-line>br {
|
#output-text.html-output .cm-line>br {
|
||||||
display: none;
|
display: none;
|
||||||
|
|||||||
@ -56,6 +56,32 @@ module.exports = {
|
|||||||
browser.expect.element("//li[contains(@class, 'operation') and text()='Play Media']").to.be.present;
|
browser.expect.element("//li[contains(@class, 'operation') and text()='Play Media']").to.be.present;
|
||||||
browser.expect.element("//li[contains(@class, 'operation') and text()='Disassemble x86']").to.be.present;
|
browser.expect.element("//li[contains(@class, 'operation') and text()='Disassemble x86']").to.be.present;
|
||||||
browser.expect.element("//li[contains(@class, 'operation') and text()='Register']").to.be.present;
|
browser.expect.element("//li[contains(@class, 'operation') and text()='Register']").to.be.present;
|
||||||
|
browser.expect.element("//li[contains(@class, 'operation') and text()='Escape Smart Characters']").to.be.present;
|
||||||
|
},
|
||||||
|
|
||||||
|
"Operation popover descriptions render HTML safely": browser => {
|
||||||
|
const favouritesCat = "//a[contains(@class, 'category-title') and contains(@data-target, '#catFavourites')]",
|
||||||
|
op = "//ul[@id='search-results']//li[contains(@class, 'operation') and contains(., 'Escape Smart Characters')]";
|
||||||
|
|
||||||
|
browser
|
||||||
|
.useCss()
|
||||||
|
.clearValue("#search")
|
||||||
|
.setValue("#search", "Escape Smart Characters")
|
||||||
|
.useXpath()
|
||||||
|
.waitForElementVisible(op, 1000)
|
||||||
|
.moveToElement(op, 10, 10)
|
||||||
|
.useCss()
|
||||||
|
.waitForElementVisible(".popover-body code:last-of-type", 1000)
|
||||||
|
.expect.element(".popover-body code:last-of-type").text.to.contain("\"Hello\" -- world...");
|
||||||
|
|
||||||
|
browser
|
||||||
|
.useCss()
|
||||||
|
.moveToElement("#operations .title", 1, 1)
|
||||||
|
.waitForElementNotPresent(".popover-body", 1000)
|
||||||
|
.clearValue("#search")
|
||||||
|
.useXpath()
|
||||||
|
.getLocationInView(favouritesCat)
|
||||||
|
.click(favouritesCat);
|
||||||
},
|
},
|
||||||
|
|
||||||
"Recipe can be run": browser => {
|
"Recipe can be run": browser => {
|
||||||
|
|||||||
@ -80,8 +80,8 @@ module.exports = {
|
|||||||
testOpHtml(browser, "Bombe", "XTSYN WAEUG EZALY NRQIM AMLZX MFUOD AWXLY LZCUZ QOQBQ JLCPK NDDRW F", "table tr:last-child td:first-child", "ECG", ["3-rotor", "LEYJVCNIXWPBQMDRTAKZGFUHOS", "BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "ESOVPZJAYQUIRHXLNFTGKDCMWB<K", "AY BR CU DH EQ FS GL IP JX KN MO TZ VW", "HELLO CYBER CHEFU SER", 0, true]);
|
testOpHtml(browser, "Bombe", "XTSYN WAEUG EZALY NRQIM AMLZX MFUOD AWXLY LZCUZ QOQBQ JLCPK NDDRW F", "table tr:last-child td:first-child", "ECG", ["3-rotor", "LEYJVCNIXWPBQMDRTAKZGFUHOS", "BDFHJLCPRTXVZNYEIWGAKMUSQO<W", "AJDKSIRUXBLHWTMCQGZNPYFVOE<F", "ESOVPZJAYQUIRHXLNFTGKDCMWB<K", "AY BR CU DH EQ FS GL IP JX KN MO TZ VW", "HELLO CYBER CHEFU SER", 0, true]);
|
||||||
testOp(browser, ["Bzip2 Compress", "To Hex"], "test input", "42 5a 68 39 31 41 59 26 53 59 cf 96 82 1d 00 00 03 91 80 40 00 02 21 4e 00 20 00 21 90 c2 10 c0 88 33 92 8e df 17 72 45 38 50 90 cf 96 82 1d");
|
testOp(browser, ["Bzip2 Compress", "To Hex"], "test input", "42 5a 68 39 31 41 59 26 53 59 cf 96 82 1d 00 00 03 91 80 40 00 02 21 4e 00 20 00 21 90 c2 10 c0 88 33 92 8e df 17 72 45 38 50 90 cf 96 82 1d");
|
||||||
testOp(browser, ["From Hex", "Bzip2 Decompress"], "425a68393141592653597b0884b7000003038000008200ce00200021a647a4218013709517c5dc914e14241ec2212dc0", "test_output", [[], [true]]);
|
testOp(browser, ["From Hex", "Bzip2 Decompress"], "425a68393141592653597b0884b7000003038000008200ce00200021a647a4218013709517c5dc914e14241ec2212dc0", "test_output", [[], [true]]);
|
||||||
// testOp(browser, "CBOR Decode", "test input", "test output");
|
testOp(browser, ["From Hex", "CBOR Decode"], "f9 3e 00", "1.5");
|
||||||
// testOp(browser, "CBOR Encode", "test input", "test output");
|
testOp(browser, ["CBOR Encode", "To Hex"], "1.5", "f9 3e 00");
|
||||||
testOp(browser, "CRC Checksum", "test input", "77c7", ["CRC-16"]);
|
testOp(browser, "CRC Checksum", "test input", "77c7", ["CRC-16"]);
|
||||||
testOp(browser, "CRC Checksum", "test input", "29822bc8", ["CRC-32"]);
|
testOp(browser, "CRC Checksum", "test input", "29822bc8", ["CRC-32"]);
|
||||||
testOp(browser, "CRC Checksum", "test input", "9d", ["CRC-8"]);
|
testOp(browser, "CRC Checksum", "test input", "9d", ["CRC-8"]);
|
||||||
@ -218,6 +218,7 @@ module.exports = {
|
|||||||
testOpHtml(browser, "JSON Beautify", "{a:1}", ".json-dict .json-literal", "1");
|
testOpHtml(browser, "JSON Beautify", "{a:1}", ".json-dict .json-literal", "1");
|
||||||
// testOp(browser, "JSON Minify", "test input", "test_output");
|
// testOp(browser, "JSON Minify", "test input", "test_output");
|
||||||
// testOp(browser, "JSON to CSV", "test input", "test_output");
|
// testOp(browser, "JSON to CSV", "test input", "test_output");
|
||||||
|
testOp(browser, "Jsonata Query", '{"a": "SGVsbG8gV29ybGQh"}', '"Hello World!"', ["$base64decode($.a)"]);
|
||||||
// testOp(browser, "JWT Decode", "test input", "test_output");
|
// testOp(browser, "JWT Decode", "test input", "test_output");
|
||||||
// testOp(browser, "JWT Sign", "test input", "test_output");
|
// testOp(browser, "JWT Sign", "test input", "test_output");
|
||||||
// testOp(browser, "JWT Verify", "test input", "test_output");
|
// testOp(browser, "JWT Verify", "test input", "test_output");
|
||||||
@ -277,12 +278,12 @@ module.exports = {
|
|||||||
// testOp(browser, "Parse TLV", "test input", "test_output");
|
// testOp(browser, "Parse TLV", "test input", "test_output");
|
||||||
testOpHtml(browser, "Parse UDP", "04 89 00 35 00 2c 01 01", "tr:last-child td:last-child", "0x0101");
|
testOpHtml(browser, "Parse UDP", "04 89 00 35 00 2c 01 01", "tr:last-child td:last-child", "0x0101");
|
||||||
// testOp(browser, "Parse UNIX file permissions", "test input", "test_output");
|
// testOp(browser, "Parse UNIX file permissions", "test input", "test_output");
|
||||||
// testOp(browser, "Parse URI", "test input", "test_output");
|
testOp(browser, "Parse URI", "https://example.com/?constructor=ok&__proto__=hello", /Arguments:\s+constructor = ok\s+__proto__\s+= hello/);
|
||||||
// testOp(browser, "Parse User Agent", "test input", "test_output");
|
testOp(browser, "Parse User Agent", "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0 ", /Architecture: amd64/);
|
||||||
// testOp(browser, "Parse X.509 certificate", "test input", "test_output");
|
// testOp(browser, "Parse X.509 certificate", "test input", "test_output");
|
||||||
testOpFile(browser, "Play Media", "files/mp3example.mp3", "audio", "");
|
testOpFile(browser, "Play Media", "files/mp3example.mp3", "audio", "");
|
||||||
// testOp(browser, "Power Set", "test input", "test_output");
|
// testOp(browser, "Power Set", "test input", "test_output");
|
||||||
// testOp(browser, "Protobuf Decode", "test input", "test_output");
|
testOp(browser, ["From Hex", "Protobuf Decode"], "0d1c0000001203596f751a024d65202b2a0a0a066162633132331200", /"1": "abc123"/, [[], ["", false, false]]);
|
||||||
// testOp(browser, "Pseudo-Random Number Generator", "test input", "test_output");
|
// testOp(browser, "Pseudo-Random Number Generator", "test input", "test_output");
|
||||||
// testOp(browser, "RC2 Decrypt", "test input", "test_output");
|
// testOp(browser, "RC2 Decrypt", "test input", "test_output");
|
||||||
// testOp(browser, "RC2 Encrypt", "test input", "test_output");
|
// testOp(browser, "RC2 Encrypt", "test input", "test_output");
|
||||||
@ -345,8 +346,8 @@ module.exports = {
|
|||||||
// testOp(browser, "Strip HTTP headers", "test input", "test_output");
|
// testOp(browser, "Strip HTTP headers", "test input", "test_output");
|
||||||
// testOp(browser, "Subsection", "test input", "test_output");
|
// testOp(browser, "Subsection", "test input", "test_output");
|
||||||
// testOp(browser, "Substitute", "test input", "test_output");
|
// testOp(browser, "Substitute", "test input", "test_output");
|
||||||
// testOp(browser, "Subtract", "test input", "test_output");
|
testOp(browser, "Subtract", "321,123,test", "198", ["Comma"]);
|
||||||
// testOp(browser, "Sum", "test input", "test_output");
|
testOp(browser, "Sum", "321,123,test", "444", ["Comma"]);
|
||||||
// testOp(browser, "Swap endianness", "test input", "test_output");
|
// testOp(browser, "Swap endianness", "test input", "test_output");
|
||||||
// testOp(browser, "Symmetric Difference", "test input", "test_output");
|
// testOp(browser, "Symmetric Difference", "test input", "test_output");
|
||||||
testOpHtml(browser, "Syntax highlighter", "var a = [4,5,6]", ".hljs-selector-attr", "[4,5,6]");
|
testOpHtml(browser, "Syntax highlighter", "var a = [4,5,6]", ".hljs-selector-attr", "[4,5,6]");
|
||||||
@ -494,7 +495,22 @@ function testOpImage(browser, opName, filename, args=[]) {
|
|||||||
|
|
||||||
browser
|
browser
|
||||||
.waitForElementVisible("#output-html img")
|
.waitForElementVisible("#output-html img")
|
||||||
.expect.element("#output-html img").to.have.css("width").which.matches(/^[^0]\d*px/);
|
.expect.element("#output-html img").to.have.css("width").which.matches(/^(?!0+(?:\.0+)?px$)\d+(?:\.\d+)?px$/);
|
||||||
|
|
||||||
|
browser.execute(function() {
|
||||||
|
const output = document.getElementById("output-html");
|
||||||
|
const img = output.querySelector("img");
|
||||||
|
const outputRect = output.getBoundingClientRect();
|
||||||
|
const imgRect = img.getBoundingClientRect();
|
||||||
|
|
||||||
|
return {
|
||||||
|
imageFitsWidth: imgRect.width <= outputRect.width,
|
||||||
|
imageFitsHeight: imgRect.height <= outputRect.height,
|
||||||
|
};
|
||||||
|
}, [], function({value}) {
|
||||||
|
browser.expect(value.imageFitsWidth).to.be.equal(true);
|
||||||
|
browser.expect(value.imageFitsHeight).to.be.equal(true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @function
|
/** @function
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import "./tests/NodeDish.mjs";
|
|||||||
import "./tests/Utils.mjs";
|
import "./tests/Utils.mjs";
|
||||||
import "./tests/Categories.mjs";
|
import "./tests/Categories.mjs";
|
||||||
import "./tests/lib/BigIntUtils.mjs";
|
import "./tests/lib/BigIntUtils.mjs";
|
||||||
|
import "./tests/lib/ChartsProtocolPrototypePollution.mjs";
|
||||||
import "./tests/lib/Thrift.mjs";
|
import "./tests/lib/Thrift.mjs";
|
||||||
|
|
||||||
const testStatus = {
|
const testStatus = {
|
||||||
|
|||||||
@ -9,4 +9,23 @@ TestRegister.addApiTests([
|
|||||||
assert(dish.presentAs);
|
assert(dish.presentAs);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
it("Disk - should not error on serialized BigNumber (0)", () => {
|
||||||
|
const dish = new Dish({ s: 1, e: 0, c: [0] }, Dish.BIG_NUMBER);
|
||||||
|
assert.strictEqual(dish.value.toString(), "0");
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("Dish - should not error on serialized BigNumber (1)", () => {
|
||||||
|
const dish = new Dish({ c: [1], e: 0, s: 1 }, Dish.BIG_NUMBER);
|
||||||
|
assert.strictEqual(dish.value.toString(), "1");
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("Dish - should not error on serialized BigNumber (-100)", () => {
|
||||||
|
const dish = new Dish({ s: -1, e: 2, c: [100] }, Dish.BIG_NUMBER);
|
||||||
|
assert.strictEqual(dish.value.toString(), "-100");
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("Dish - should not error on serialized BigNumber (NaN)", () => {
|
||||||
|
const dish = new Dish({ s: null, e: null, c: null }, Dish.BIG_NUMBER);
|
||||||
|
assert.strictEqual(dish.value.toString(), "NaN");
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -65,6 +65,42 @@ TestRegister.addApiTests([
|
|||||||
assert.strictEqual(result.toString(), "493e8136b759370a415ef2cf2f7a69690441ff86592aba082bc2e2e0");
|
assert.strictEqual(result.toString(), "493e8136b759370a415ef2cf2f7a69690441ff86592aba082bc2e2e0");
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
it("Composable Dish: toBase32 should support non-BMP Unicode alphabets", () => {
|
||||||
|
const alphabet = "🀇🀈🀉🀊🀋🀌🀍🀎🀏🀙🀚🀛🀜🀝🀞🀟🀠🀡🀐🀑🀒🀓🀔🀕🀖🀗🀘🀀🀁🀂🀃🀅";
|
||||||
|
|
||||||
|
const result = new Dish("hello")
|
||||||
|
.apply(toBase32, {alphabet})
|
||||||
|
.toString();
|
||||||
|
|
||||||
|
// Should not contain replacement characters
|
||||||
|
assert.equal(result.includes("<22>"), false);
|
||||||
|
|
||||||
|
// Should contain only symbols from the alphabet
|
||||||
|
for (const ch of Array.from(result)) {
|
||||||
|
assert.ok(Array.from(alphabet).includes(ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
// "hello" => 8 Base32 symbols
|
||||||
|
assert.equal(Array.from(result).length, 8);
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("Composable Dish: toBase32 should omit padding for 32-character Unicode alphabets", () => {
|
||||||
|
const alphabet = "🀇🀈🀉🀊🀋🀌🀍🀎🀏🀙🀚🀛🀜🀝🀞🀟🀠🀡🀐🀑🀒🀓🀔🀕🀖🀗🀘🀀🀁🀂🀃🀅";
|
||||||
|
|
||||||
|
const result = new Dish("hell")
|
||||||
|
.apply(toBase32, {alphabet})
|
||||||
|
.toString();
|
||||||
|
|
||||||
|
// Should not leak undefined from array indexing
|
||||||
|
assert.equal(result.includes("undefined"), false);
|
||||||
|
|
||||||
|
// Should not contain replacement characters
|
||||||
|
assert.equal(result.includes("<22>"), false);
|
||||||
|
|
||||||
|
// Unpadded Base32 output for 4-byte input should be 7 symbols
|
||||||
|
assert.equal(Array.from(result).length, 7);
|
||||||
|
}),
|
||||||
|
|
||||||
it("Dish translation: ArrayBuffer to ArrayBuffer", () => {
|
it("Dish translation: ArrayBuffer to ArrayBuffer", () => {
|
||||||
const dish = new Dish(new ArrayBuffer(10), 4);
|
const dish = new Dish(new ArrayBuffer(10), 4);
|
||||||
dish.get("array buffer");
|
dish.get("array buffer");
|
||||||
|
|||||||
90
tests/node/tests/lib/ChartsProtocolPrototypePollution.mjs
Normal file
90
tests/node/tests/lib/ChartsProtocolPrototypePollution.mjs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import TestRegister from "../../../lib/TestRegister.mjs";
|
||||||
|
import {getSeriesValues} from "../../../../src/core/lib/Charts.mjs";
|
||||||
|
import {objToTable} from "../../../../src/core/lib/Protocol.mjs";
|
||||||
|
import SeriesChart from "../../../../src/core/operations/SeriesChart.mjs";
|
||||||
|
import ParseUDP from "../../../../src/core/operations/ParseUDP.mjs";
|
||||||
|
import it from "../../assertionHandler.mjs";
|
||||||
|
import assert from "assert";
|
||||||
|
|
||||||
|
const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
||||||
|
|
||||||
|
TestRegister.addApiTests([
|
||||||
|
it("Charts: should not pollute Object.prototype from a __proto__ series name", () => {
|
||||||
|
const xVal = "<img src=x onerror=alert(1)>";
|
||||||
|
delete Object.prototype[xVal];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = getSeriesValues(`__proto__,${xVal},1`, "\n", ",", false);
|
||||||
|
|
||||||
|
assert.equal(Object.prototype[xVal], undefined);
|
||||||
|
assert.deepEqual(result.xValues, [xVal]);
|
||||||
|
assert.equal(result.series.length, 1);
|
||||||
|
assert.equal(result.series[0].name, "__proto__");
|
||||||
|
assert.equal(Object.getPrototypeOf(result.series[0].data), null);
|
||||||
|
assert(hasOwn(result.series[0].data, xVal));
|
||||||
|
assert.equal(result.series[0].data[xVal], 1);
|
||||||
|
} finally {
|
||||||
|
delete Object.prototype[xVal];
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("Charts: should keep __proto__ x-axis names as own data keys", () => {
|
||||||
|
const result = getSeriesValues("safe,__proto__,1", "\n", ",", false);
|
||||||
|
|
||||||
|
assert.equal(result.series.length, 1);
|
||||||
|
assert.equal(Object.getPrototypeOf(result.series[0].data), null);
|
||||||
|
assert(hasOwn(result.series[0].data, "__proto__"));
|
||||||
|
assert.equal(result.series[0].data.__proto__, 1);
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("Protocol: should ignore inherited properties when rendering tables", () => {
|
||||||
|
const inheritedKey = "<img src=x onerror=alert(1)>";
|
||||||
|
delete Object.prototype[inheritedKey];
|
||||||
|
|
||||||
|
try {
|
||||||
|
Object.prototype[inheritedKey] = "polluted";
|
||||||
|
|
||||||
|
const html = objToTable({safe: "value"});
|
||||||
|
|
||||||
|
assert(!html.includes(inheritedKey));
|
||||||
|
assert(!html.includes("polluted"));
|
||||||
|
assert(html.includes("safe"));
|
||||||
|
assert(html.includes("value"));
|
||||||
|
} finally {
|
||||||
|
delete Object.prototype[inheritedKey];
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("Protocol: should escape table keys and scalar values", () => {
|
||||||
|
const obj = {
|
||||||
|
"<b>field</b>": "<img src=x onerror=alert(1)>",
|
||||||
|
};
|
||||||
|
|
||||||
|
const html = objToTable(obj);
|
||||||
|
|
||||||
|
assert(!html.includes("<b>field</b>"));
|
||||||
|
assert(!html.includes("<img src=x onerror=alert(1)>"));
|
||||||
|
assert(html.includes("<b>field</b>"));
|
||||||
|
assert(html.includes("<img src=x onerror=alert(1)>"));
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("Series chart and Parse UDP: should not expose polluted prototype data as HTML", () => {
|
||||||
|
const xVal = "<img src=x onerror=alert(document.domain)>";
|
||||||
|
delete Object.prototype[xVal];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const chartHtml = new SeriesChart().run(
|
||||||
|
`__proto__,${xVal},1`,
|
||||||
|
["Line feed", "Comma", "", 1, "red"]
|
||||||
|
);
|
||||||
|
assert.equal(Object.prototype[xVal], undefined);
|
||||||
|
|
||||||
|
const parseUDP = new ParseUDP();
|
||||||
|
const tableHtml = parseUDP.present(parseUDP.run(chartHtml, ["Raw"]));
|
||||||
|
|
||||||
|
assert(!/<img|onerror|alert\(/.test(tableHtml));
|
||||||
|
} finally {
|
||||||
|
delete Object.prototype[xVal];
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
]);
|
||||||
@ -109,6 +109,38 @@ TestRegister.addApiTests([
|
|||||||
assert.equal(3 + result, 35);
|
assert.equal(3 + result, 35);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
it("toBase32: should support non-BMP Unicode alphabets", () => {
|
||||||
|
const alphabet = "🀇🀈🀉🀊🀋🀌🀍🀎🀏🀙🀚🀛🀜🀝🀞🀟🀠🀡🀐🀑🀒🀓🀔🀕🀖🀗🀘🀀🀁🀂🀃🀅";
|
||||||
|
|
||||||
|
const result = chef.toBase32("hello", {alphabet}).toString();
|
||||||
|
|
||||||
|
// Should not contain replacement characters
|
||||||
|
assert.equal(result.includes("<22>"), false);
|
||||||
|
|
||||||
|
// Should contain only symbols from the alphabet
|
||||||
|
for (const ch of Array.from(result)) {
|
||||||
|
assert.ok(Array.from(alphabet).includes(ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
// "hello" => 8 Base32 symbols
|
||||||
|
assert.equal(Array.from(result).length, 8);
|
||||||
|
}),
|
||||||
|
|
||||||
|
it("toBase32: should omit padding for 32-character Unicode alphabets", () => {
|
||||||
|
const alphabet = "🀇🀈🀉🀊🀋🀌🀍🀎🀏🀙🀚🀛🀜🀝🀞🀟🀠🀡🀐🀑🀒🀓🀔🀕🀖🀗🀘🀀🀁🀂🀃🀅";
|
||||||
|
|
||||||
|
const result = chef.toBase32("hell", {alphabet}).toString();
|
||||||
|
|
||||||
|
// Should not leak undefined from array indexing
|
||||||
|
assert.equal(result.includes("undefined"), false);
|
||||||
|
|
||||||
|
// Should not contain replacement characters
|
||||||
|
assert.equal(result.includes("<22>"), false);
|
||||||
|
|
||||||
|
// Unpadded Base32 output for 4-byte input should be 7 symbols
|
||||||
|
assert.equal(Array.from(result).length, 7);
|
||||||
|
}),
|
||||||
|
|
||||||
it("chef.help: should exist", () => {
|
it("chef.help: should exist", () => {
|
||||||
assert(chef.help);
|
assert(chef.help);
|
||||||
}),
|
}),
|
||||||
@ -136,7 +168,7 @@ TestRegister.addApiTests([
|
|||||||
|
|
||||||
it("chef.help: returns multiple results", () => {
|
it("chef.help: returns multiple results", () => {
|
||||||
const result = chef.help("base 64");
|
const result = chef.help("base 64");
|
||||||
assert.strictEqual(result.length, 13);
|
assert.strictEqual(result.length, 14);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
it("chef.help: looks in description for matches too", () => {
|
it("chef.help: looks in description for matches too", () => {
|
||||||
|
|||||||
@ -605,8 +605,9 @@ Top Drawer`, {
|
|||||||
|
|
||||||
it("Generate HOTP", () => {
|
it("Generate HOTP", () => {
|
||||||
const result = chef.generateHOTP("JBSWY3DPEHPK3PXP", {
|
const result = chef.generateHOTP("JBSWY3DPEHPK3PXP", {
|
||||||
|
name: "Account",
|
||||||
});
|
});
|
||||||
const expected = `URI: otpauth://hotp/?secret=JBSWY3DPEHPK3PXP&algorithm=SHA1&digits=6&counter=0
|
const expected = `URI: otpauth://hotp/Account?secret=JBSWY3DPEHPK3PXP&algorithm=SHA1&digits=6&counter=0
|
||||||
|
|
||||||
Password: 282760`;
|
Password: 282760`;
|
||||||
assert.strictEqual(result.toString(), expected);
|
assert.strictEqual(result.toString(), expected);
|
||||||
@ -728,6 +729,18 @@ Arguments:
|
|||||||
assert.strictEqual(result.toString(), expected);
|
assert.strictEqual(result.toString(), expected);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
it("Parse URI with constructor and __proto__ arguments", () => {
|
||||||
|
const result = chef.parseURI("https://example.com/?constructor=ok&__proto__=hello");
|
||||||
|
const expected = `Protocol: https:
|
||||||
|
Hostname: example.com
|
||||||
|
Path name: /
|
||||||
|
Arguments:
|
||||||
|
\tconstructor = ok
|
||||||
|
\t__proto__ = hello
|
||||||
|
`;
|
||||||
|
assert.strictEqual(result.toString(), expected);
|
||||||
|
}),
|
||||||
|
|
||||||
it("Parse user agent", () => {
|
it("Parse user agent", () => {
|
||||||
const result = chef.parseUserAgent("Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0 ");
|
const result = chef.parseUserAgent("Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0 ");
|
||||||
const expected = `Browser
|
const expected = `Browser
|
||||||
|
|||||||
@ -1,218 +0,0 @@
|
|||||||
/* eslint no-console: 0 */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test Runner
|
|
||||||
*
|
|
||||||
* For running the tests in the test register.
|
|
||||||
*
|
|
||||||
* @author tlwr [toby@toby.codes]
|
|
||||||
* @author n1474335 [n1474335@gmail.com]
|
|
||||||
* @copyright Crown Copyright 2017
|
|
||||||
* @license Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
import "../lib/wasmFetchPolyfill.mjs";
|
|
||||||
import { setLongTestFailure, logTestReport } from "../lib/utils.mjs";
|
|
||||||
|
|
||||||
import TestRegister from "../lib/TestRegister.mjs";
|
|
||||||
import "./tests/A1Z26CipherDecode.mjs";
|
|
||||||
import "./tests/AESKeyWrap.mjs";
|
|
||||||
import "./tests/AnalyseUUID.mjs";
|
|
||||||
import "./tests/AlternatingCaps.mjs";
|
|
||||||
import "./tests/AvroToJSON.mjs";
|
|
||||||
import "./tests/BaconCipher.mjs";
|
|
||||||
import "./tests/Base32.mjs";
|
|
||||||
import "./tests/Base45.mjs";
|
|
||||||
import "./tests/Base58.mjs";
|
|
||||||
import "./tests/Base62.mjs";
|
|
||||||
import "./tests/Base64.mjs";
|
|
||||||
import "./tests/Base85.mjs";
|
|
||||||
import "./tests/Base92.mjs";
|
|
||||||
import "./tests/BCD.mjs";
|
|
||||||
import "./tests/Bech32.mjs";
|
|
||||||
import "./tests/BitwiseOp.mjs";
|
|
||||||
import "./tests/BLAKE2b.mjs";
|
|
||||||
import "./tests/BLAKE2s.mjs";
|
|
||||||
import "./tests/BLAKE3.mjs";
|
|
||||||
import "./tests/Bombe.mjs";
|
|
||||||
import "./tests/BSON.mjs";
|
|
||||||
import "./tests/ByteRepr.mjs";
|
|
||||||
import "./tests/CaesarBoxCipher.mjs";
|
|
||||||
import "./tests/CaretMdecode.mjs";
|
|
||||||
import "./tests/CartesianProduct.mjs";
|
|
||||||
import "./tests/CBORDecode.mjs";
|
|
||||||
import "./tests/CBOREncode.mjs";
|
|
||||||
import "./tests/CetaceanCipherDecode.mjs";
|
|
||||||
import "./tests/CetaceanCipherEncode.mjs";
|
|
||||||
import "./tests/ChaCha.mjs";
|
|
||||||
import "./tests/ChangeIPFormat.mjs";
|
|
||||||
import "./tests/CharEnc.mjs";
|
|
||||||
import "./tests/Charts.mjs";
|
|
||||||
import "./tests/Ciphers.mjs";
|
|
||||||
import "./tests/CipherSaber2.mjs";
|
|
||||||
import "./tests/CMAC.mjs";
|
|
||||||
import "./tests/Code.mjs";
|
|
||||||
import "./tests/Colossus.mjs";
|
|
||||||
import "./tests/Comment.mjs";
|
|
||||||
import "./tests/Compress.mjs";
|
|
||||||
import "./tests/ConditionalJump.mjs";
|
|
||||||
import "./tests/ConvertCoordinateFormat.mjs";
|
|
||||||
import "./tests/ConvertLeetSpeak.mjs";
|
|
||||||
import "./tests/ConvertToNATOAlphabet.mjs";
|
|
||||||
import "./tests/CRCChecksum.mjs";
|
|
||||||
import "./tests/Crypt.mjs";
|
|
||||||
import "./tests/CSV.mjs";
|
|
||||||
import "./tests/DateTime.mjs";
|
|
||||||
import "./tests/DefangIP.mjs";
|
|
||||||
import "./tests/DisassembleARM.mjs";
|
|
||||||
import "./tests/DropNthBytes.mjs";
|
|
||||||
import "./tests/ECDSA.mjs";
|
|
||||||
import "./tests/ELFInfo.mjs";
|
|
||||||
import "./tests/Enigma.mjs";
|
|
||||||
import "./tests/EscapeSmartCharacters.mjs";
|
|
||||||
import "./tests/ExtractAudioMetadata.mjs";
|
|
||||||
import "./tests/ExtractEmailAddresses.mjs";
|
|
||||||
import "./tests/ExtractHashes.mjs";
|
|
||||||
import "./tests/ExtractIPAddresses.mjs";
|
|
||||||
import "./tests/Fernet.mjs";
|
|
||||||
import "./tests/Float.mjs";
|
|
||||||
import "./tests/FileTree.mjs";
|
|
||||||
import "./tests/FletcherChecksum.mjs";
|
|
||||||
import "./tests/Fork.mjs";
|
|
||||||
import "./tests/FromDecimal.mjs";
|
|
||||||
import "./tests/GenerateAllChecksums.mjs";
|
|
||||||
import "./tests/GenerateAllHashes.mjs";
|
|
||||||
import "./tests/GenerateDeBruijnSequence.mjs";
|
|
||||||
import "./tests/GenerateQRCode.mjs";
|
|
||||||
import "./tests/GetAllCasings.mjs";
|
|
||||||
import "./tests/GOST.mjs";
|
|
||||||
import "./tests/Gunzip.mjs";
|
|
||||||
import "./tests/Gzip.mjs";
|
|
||||||
import "./tests/Hash.mjs";
|
|
||||||
import "./tests/HASSH.mjs";
|
|
||||||
import "./tests/HaversineDistance.mjs";
|
|
||||||
import "./tests/Hex.mjs";
|
|
||||||
import "./tests/Hexdump.mjs";
|
|
||||||
import "./tests/HKDF.mjs";
|
|
||||||
import "./tests/Image.mjs";
|
|
||||||
import "./tests/IndexOfCoincidence.mjs";
|
|
||||||
import "./tests/JA3Fingerprint.mjs";
|
|
||||||
import "./tests/JA4.mjs";
|
|
||||||
import "./tests/JA3SFingerprint.mjs";
|
|
||||||
import "./tests/Jsonata.mjs";
|
|
||||||
import "./tests/JSONBeautify.mjs";
|
|
||||||
import "./tests/JSONMinify.mjs";
|
|
||||||
import "./tests/JSONtoCSV.mjs";
|
|
||||||
import "./tests/Jump.mjs";
|
|
||||||
import "./tests/JWK.mjs";
|
|
||||||
import "./tests/JWTDecode.mjs";
|
|
||||||
import "./tests/JWTSign.mjs";
|
|
||||||
import "./tests/JWTVerify.mjs";
|
|
||||||
import "./tests/LevenshteinDistance.mjs";
|
|
||||||
import "./tests/Lorenz.mjs";
|
|
||||||
import "./tests/LS47.mjs";
|
|
||||||
import "./tests/LuhnChecksum.mjs";
|
|
||||||
import "./tests/LZNT1Decompress.mjs";
|
|
||||||
import "./tests/LZString.mjs";
|
|
||||||
import "./tests/Magic.mjs";
|
|
||||||
import "./tests/Media.mjs";
|
|
||||||
import "./tests/MIMEDecoding.mjs";
|
|
||||||
import "./tests/Modhex.mjs";
|
|
||||||
import "./tests/MorseCode.mjs";
|
|
||||||
import "./tests/MS.mjs";
|
|
||||||
import "./tests/MultipleBombe.mjs";
|
|
||||||
import "./tests/MurmurHash3.mjs";
|
|
||||||
import "./tests/NetBIOS.mjs";
|
|
||||||
import "./tests/NormaliseUnicode.mjs";
|
|
||||||
import "./tests/NTLM.mjs";
|
|
||||||
import "./tests/OTP.mjs";
|
|
||||||
import "./tests/ParseEthernetFrame.mjs";
|
|
||||||
import "./tests/ParseIPv4Header.mjs";
|
|
||||||
import "./tests/ParseIPRange.mjs";
|
|
||||||
import "./tests/ParseObjectIDTimestamp.mjs";
|
|
||||||
import "./tests/ParseQRCode.mjs";
|
|
||||||
import "./tests/ParseSSHHostKey.mjs";
|
|
||||||
import "./tests/ParseTCP.mjs";
|
|
||||||
import "./tests/ParseTLSRecord.mjs";
|
|
||||||
import "./tests/ParseTLV.mjs";
|
|
||||||
import "./tests/ParseUDP.mjs";
|
|
||||||
import "./tests/PEMtoHex.mjs";
|
|
||||||
import "./tests/PGP.mjs";
|
|
||||||
import "./tests/PHP.mjs";
|
|
||||||
import "./tests/ParityBit.mjs";
|
|
||||||
import "./tests/PHPSerialize.mjs";
|
|
||||||
import "./tests/PowerSet.mjs";
|
|
||||||
import "./tests/Protobuf.mjs";
|
|
||||||
import "./tests/PubKeyFromCert.mjs";
|
|
||||||
import "./tests/PubKeyFromPrivKey.mjs";
|
|
||||||
import "./tests/Rabbit.mjs";
|
|
||||||
import "./tests/RAKE.mjs";
|
|
||||||
import "./tests/Regex.mjs";
|
|
||||||
import "./tests/Register.mjs";
|
|
||||||
import "./tests/RemoveANSIEscapeCodes.mjs";
|
|
||||||
import "./tests/RegularExpression.mjs";
|
|
||||||
import "./tests/RenderMarkdown.mjs";
|
|
||||||
import "./tests/RisonEncodeDecode.mjs";
|
|
||||||
import "./tests/Rotate.mjs";
|
|
||||||
import "./tests/RSA.mjs";
|
|
||||||
import "./tests/Salsa20.mjs";
|
|
||||||
import "./tests/XSalsa20.mjs";
|
|
||||||
import "./tests/SeqUtils.mjs";
|
|
||||||
import "./tests/SetDifference.mjs";
|
|
||||||
import "./tests/SetIntersection.mjs";
|
|
||||||
import "./tests/SetUnion.mjs";
|
|
||||||
import "./tests/Shuffle.mjs";
|
|
||||||
import "./tests/SIGABA.mjs";
|
|
||||||
import "./tests/SM2.mjs";
|
|
||||||
import "./tests/SM4.mjs";
|
|
||||||
import "./tests/RC6.mjs";
|
|
||||||
// import "./tests/SplitColourChannels.mjs"; // Cannot test operations that use the File type yet
|
|
||||||
import "./tests/SQLBeautify.mjs";
|
|
||||||
import "./tests/StrUtils.mjs";
|
|
||||||
import "./tests/StripIPv4Header.mjs";
|
|
||||||
import "./tests/StripTCPHeader.mjs";
|
|
||||||
import "./tests/StripUDPHeader.mjs";
|
|
||||||
import "./tests/Subsection.mjs";
|
|
||||||
import "./tests/SwapCase.mjs";
|
|
||||||
import "./tests/SymmetricDifference.mjs";
|
|
||||||
import "./tests/TakeNthBytes.mjs";
|
|
||||||
import "./tests/Template.mjs";
|
|
||||||
import "./tests/TextEncodingBruteForce.mjs";
|
|
||||||
import "./tests/TextIntegerConverter.mjs";
|
|
||||||
import "./tests/Thrift.mjs";
|
|
||||||
import "./tests/ToFromInsensitiveRegex.mjs";
|
|
||||||
import "./tests/TranslateDateTimeFormat.mjs";
|
|
||||||
import "./tests/Typex.mjs";
|
|
||||||
import "./tests/UnescapeString.mjs";
|
|
||||||
import "./tests/Unicode.mjs";
|
|
||||||
import "./tests/Wrap.mjs";
|
|
||||||
import "./tests/URLEncodeDecode.mjs";
|
|
||||||
import "./tests/RSA.mjs";
|
|
||||||
import "./tests/CBOREncode.mjs";
|
|
||||||
import "./tests/CBORDecode.mjs";
|
|
||||||
import "./tests/JA3Fingerprint.mjs";
|
|
||||||
import "./tests/JA3SFingerprint.mjs";
|
|
||||||
import "./tests/HASSH.mjs";
|
|
||||||
import "./tests/JSONtoYAML.mjs";
|
|
||||||
|
|
||||||
// Cannot test operations that use the File type yet
|
|
||||||
// import "./tests/SplitColourChannels.mjs";
|
|
||||||
import "./tests/YARA.mjs";
|
|
||||||
import "./tests/ParseCSR.mjs";
|
|
||||||
import "./tests/XXTEA.mjs";
|
|
||||||
|
|
||||||
const testStatus = {
|
|
||||||
allTestsPassing: true,
|
|
||||||
counts: {
|
|
||||||
total: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
setLongTestFailure();
|
|
||||||
|
|
||||||
const logOpsTestReport = logTestReport.bind(null, testStatus);
|
|
||||||
|
|
||||||
(async function () {
|
|
||||||
const results = await TestRegister.runTests();
|
|
||||||
logOpsTestReport(results);
|
|
||||||
})();
|
|
||||||
33
tests/operations/tests/Arithmetic.mjs
Normal file
33
tests/operations/tests/Arithmetic.mjs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Tests for arithmetical operations
|
||||||
|
*
|
||||||
|
* @copyright Crown Copyright 2026
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Subtract",
|
||||||
|
input: "321,123,test",
|
||||||
|
expectedOutput: "198",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Subtract",
|
||||||
|
"args": ["Comma"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Subtract - no valid input",
|
||||||
|
input: "test",
|
||||||
|
expectedOutput: "NaN",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Subtract",
|
||||||
|
"args": ["Comma"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
501
tests/operations/tests/Ascon.mjs
Normal file
501
tests/operations/tests/Ascon.mjs
Normal file
@ -0,0 +1,501 @@
|
|||||||
|
/**
|
||||||
|
* Ascon tests.
|
||||||
|
*
|
||||||
|
* Test vectors include official NIST ACVP vectors from:
|
||||||
|
* https://github.com/usnistgov/ACVP-Server/tree/master/gen-val/json-files/Ascon-Hash256-SP800-232
|
||||||
|
* https://github.com/ascon/ascon-c (LWC_AEAD_KAT files)
|
||||||
|
*
|
||||||
|
* @author Medjedtxm
|
||||||
|
* @copyright Crown Copyright 2025
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
// ============= Ascon Hash Tests (NIST SP 800-232) =============
|
||||||
|
// Official NIST ACVP test vector
|
||||||
|
{
|
||||||
|
name: "Ascon Hash: NIST ACVP vector (msg=0x50)",
|
||||||
|
input: "P", // 0x50
|
||||||
|
expectedOutput: "b96da347d720272533a87f5a94a356155f49cdf7c0c10a3e6f346d8a2293e480",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Hash",
|
||||||
|
"args": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ascon Hash: empty input",
|
||||||
|
input: "",
|
||||||
|
expectedOutput: "0b3be5850f2f6b98caf29f8fdea89b64a1fa70aa249b8f839bd53baa304d92b2",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Hash",
|
||||||
|
"args": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ascon Hash: Hello",
|
||||||
|
input: "Hello",
|
||||||
|
expectedOutput: "c1beebe1251d562c4526d6b947cefb932998499424f6cd186e764aa0a36cddb7",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Hash",
|
||||||
|
"args": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ascon Hash: Hello, World!",
|
||||||
|
input: "Hello, World!",
|
||||||
|
expectedOutput: "f40e1ce8d4272e628e9535193f196f4ff2a720b00f6380c5d6f16b975f3a7777",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Hash",
|
||||||
|
"args": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
// ============= Ascon MAC Tests (NIST LWC_MAC_KAT_128_128.txt) =============
|
||||||
|
// Official test vectors from ascon-c: https://github.com/ascon/ascon-c/blob/main/crypto_auth/asconmacv13/LWC_MAC_KAT_128_128.txt
|
||||||
|
{
|
||||||
|
name: "Ascon MAC: NIST KAT Count=1 (empty message)",
|
||||||
|
input: "",
|
||||||
|
expectedOutput: "eac9d74bbedf8bf1eba2862b26aa6d39",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon MAC",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ascon MAC: NIST KAT Count=2 (Msg=0x10)",
|
||||||
|
input: "\x10",
|
||||||
|
expectedOutput: "e5be5b6dfb7b0e3eae00a070791947a8",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon MAC",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ascon MAC: NIST KAT Count=5 (Msg=0x10111213)",
|
||||||
|
input: "\x10\x11\x12\x13",
|
||||||
|
expectedOutput: "727f6386405a52ad7ca0669a6a885294",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon MAC",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ascon MAC: invalid key length",
|
||||||
|
input: "test",
|
||||||
|
expectedOutput: "Invalid key length: 8 bytes.\n\nAscon-Mac requires a key of exactly 16 bytes (128 bits).",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon MAC",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": "0001020304050607"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
// ============= Ascon Encrypt Tests (NIST SP 800-232) =============
|
||||||
|
// Official NIST ascon-c KAT test vector (Count=1)
|
||||||
|
// https://github.com/ascon/ascon-c/blob/main/crypto_aead/asconaead128/LWC_AEAD_KAT_128_128.txt
|
||||||
|
{
|
||||||
|
name: "Ascon Encrypt: NIST KAT Count=1 (empty PT, empty AD)",
|
||||||
|
input: "",
|
||||||
|
expectedOutput: "4f9c278211bec9316bf68f46ee8b2ec6",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Encrypt",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": "101112131415161718191a1b1c1d1e1f"},
|
||||||
|
{"option": "Hex", "string": ""},
|
||||||
|
"Raw", "Hex"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// Official NIST ascon-c KAT test vector (Count=2)
|
||||||
|
{
|
||||||
|
name: "Ascon Encrypt: NIST KAT Count=2 (empty PT, AD=0x30)",
|
||||||
|
input: "",
|
||||||
|
expectedOutput: "cccb674fe18a09a285d6ab11b35675c0",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Encrypt",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": "101112131415161718191a1b1c1d1e1f"},
|
||||||
|
{"option": "Hex", "string": "30"},
|
||||||
|
"Raw", "Hex"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// Official NIST ascon-c KAT test vector (Count=34) - PT=0x20
|
||||||
|
{
|
||||||
|
name: "Ascon Encrypt: NIST KAT Count=34 (PT=0x20, empty AD)",
|
||||||
|
input: "\x20",
|
||||||
|
expectedOutput: "e8dd576aba1cd3e6fc704de02aedb79588",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Encrypt",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": "101112131415161718191a1b1c1d1e1f"},
|
||||||
|
{"option": "Hex", "string": ""},
|
||||||
|
"Raw", "Hex"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// Official NIST ascon-c KAT test vector (Count=341) - PT + AD
|
||||||
|
{
|
||||||
|
name: "Ascon Encrypt: NIST KAT Count=341 (PT=10 bytes, AD=10 bytes)",
|
||||||
|
input: "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29",
|
||||||
|
expectedOutput: "12042996da42b4536e5a0e64692cf6041ff8c367e1423253c84c",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Encrypt",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": "101112131415161718191a1b1c1d1e1f"},
|
||||||
|
{"option": "Hex", "string": "30313233343536373839"},
|
||||||
|
"Raw", "Hex"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// Official NIST ascon-c KAT test vector (PT=16 bytes, AD=16 bytes)
|
||||||
|
{
|
||||||
|
name: "Ascon Encrypt: NIST KAT (PT=16 bytes, AD=16 bytes)",
|
||||||
|
input: "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f",
|
||||||
|
expectedOutput: "6373ebb28be97c9bac090cf399c13ef13abfc0d209e8f4844c90814d13f32c59",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Encrypt",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": "101112131415161718191a1b1c1d1e1f"},
|
||||||
|
{"option": "Hex", "string": "303132333435363738393a3b3c3d3e3f"},
|
||||||
|
"Raw", "Hex"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// https://github.com/ascon/ascon-c/blob/main/crypto_aead/asconaead128/LWC_AEAD_KAT_128_128.txt
|
||||||
|
{
|
||||||
|
name: "Ascon Encrypt: no key",
|
||||||
|
input: "test message",
|
||||||
|
expectedOutput: `Invalid key length: 0 bytes.
|
||||||
|
|
||||||
|
Ascon-AEAD128 requires a key of exactly 16 bytes (128 bits).`,
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Encrypt",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": ""},
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": ""},
|
||||||
|
"Raw", "Hex"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ascon Encrypt: invalid key length",
|
||||||
|
input: "test message",
|
||||||
|
expectedOutput: `Invalid key length: 8 bytes.
|
||||||
|
|
||||||
|
Ascon-AEAD128 requires a key of exactly 16 bytes (128 bits).`,
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Encrypt",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": "0001020304050607"},
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": ""},
|
||||||
|
"Raw", "Hex"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ascon Encrypt: no nonce",
|
||||||
|
input: "test message",
|
||||||
|
expectedOutput: `Invalid nonce length: 0 bytes.
|
||||||
|
|
||||||
|
Ascon-AEAD128 requires a nonce of exactly 16 bytes (128 bits).`,
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Encrypt",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": ""},
|
||||||
|
{"option": "Hex", "string": ""},
|
||||||
|
"Raw", "Hex"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ascon Encrypt: invalid nonce length",
|
||||||
|
input: "test message",
|
||||||
|
expectedOutput: `Invalid nonce length: 12 bytes.
|
||||||
|
|
||||||
|
Ascon-AEAD128 requires a nonce of exactly 16 bytes (128 bits).`,
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Encrypt",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b"},
|
||||||
|
{"option": "Hex", "string": ""},
|
||||||
|
"Raw", "Hex"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ascon Encrypt: basic encryption",
|
||||||
|
input: "Hello",
|
||||||
|
expectedOutput: "af14bce6b9b6588c3aa63f9ddc5a0cf5f565f358b0",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Encrypt",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": ""},
|
||||||
|
"Raw", "Hex"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ascon Encrypt: with associated data",
|
||||||
|
input: "Hello",
|
||||||
|
expectedOutput: "351880c09f9dee12c20c4ba973066bc10dd26000b6",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Encrypt",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "UTF8", "string": "metadata"},
|
||||||
|
"Raw", "Hex"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ascon Encrypt: longer message",
|
||||||
|
input: "test message",
|
||||||
|
expectedOutput: "9314a3fef6cc299a07b8c9e0f9e479ca0d1187e87345cf590adc572b",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Encrypt",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": ""},
|
||||||
|
"Raw", "Hex"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ascon Encrypt: empty plaintext",
|
||||||
|
input: "",
|
||||||
|
expectedOutput: "4427d64b8e1e1451fc445960f0839bb0",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Encrypt",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": ""},
|
||||||
|
"Raw", "Hex"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ascon Encrypt: zero key and nonce",
|
||||||
|
input: "Hello",
|
||||||
|
expectedOutput: "403281e117ebb087e2d9196552b2d123bccb7b5500",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Encrypt",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": "00000000000000000000000000000000"},
|
||||||
|
{"option": "Hex", "string": "00000000000000000000000000000000"},
|
||||||
|
{"option": "Hex", "string": ""},
|
||||||
|
"Raw", "Hex"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
// ============= Ascon Decrypt Tests =============
|
||||||
|
{
|
||||||
|
name: "Ascon Decrypt: no key",
|
||||||
|
input: "af14bce6b9b6588c3aa63f9ddc5a0cf5f565f358b0",
|
||||||
|
expectedOutput: `Invalid key length: 0 bytes.
|
||||||
|
|
||||||
|
Ascon-AEAD128 requires a key of exactly 16 bytes (128 bits).`,
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Decrypt",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": ""},
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": ""},
|
||||||
|
"Hex", "Raw"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ascon Decrypt: basic decryption",
|
||||||
|
input: "af14bce6b9b6588c3aa63f9ddc5a0cf5f565f358b0",
|
||||||
|
expectedOutput: "Hello",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Decrypt",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": ""},
|
||||||
|
"Hex", "Raw"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ascon Decrypt: with associated data",
|
||||||
|
input: "351880c09f9dee12c20c4ba973066bc10dd26000b6",
|
||||||
|
expectedOutput: "Hello",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Decrypt",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "UTF8", "string": "metadata"},
|
||||||
|
"Hex", "Raw"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ascon Decrypt: longer message",
|
||||||
|
input: "9314a3fef6cc299a07b8c9e0f9e479ca0d1187e87345cf590adc572b",
|
||||||
|
expectedOutput: "test message",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Decrypt",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": ""},
|
||||||
|
"Hex", "Raw"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ascon Decrypt: authentication failure (tampered ciphertext)",
|
||||||
|
input: "bf14bce6b9b6588c3aa63f9ddc5a0cf5f565f358b0",
|
||||||
|
expectedOutput: "Unable to decrypt: authentication failed. The ciphertext, key, nonce, or associated data may be incorrect or tampered with.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Decrypt",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": ""},
|
||||||
|
"Hex", "Raw"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ascon Decrypt: authentication failure (wrong key)",
|
||||||
|
input: "af14bce6b9b6588c3aa63f9ddc5a0cf5f565f358b0",
|
||||||
|
expectedOutput: "Unable to decrypt: authentication failed. The ciphertext, key, nonce, or associated data may be incorrect or tampered with.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Decrypt",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": "ff0102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": ""},
|
||||||
|
"Hex", "Raw"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ascon Decrypt: authentication failure (wrong associated data)",
|
||||||
|
input: "351880c09f9dee12c20c4ba973066bc10dd26000b6",
|
||||||
|
expectedOutput: "Unable to decrypt: authentication failed. The ciphertext, key, nonce, or associated data may be incorrect or tampered with.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Decrypt",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "UTF8", "string": "wrong data"},
|
||||||
|
"Hex", "Raw"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
// ============= Round-trip Tests =============
|
||||||
|
{
|
||||||
|
name: "Ascon: encrypt then decrypt round-trip",
|
||||||
|
input: "This is a test message for Ascon AEAD encryption!",
|
||||||
|
expectedOutput: "This is a test message for Ascon AEAD encryption!",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Ascon Encrypt",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": "101112131415161718191a1b1c1d1e1f"},
|
||||||
|
{"option": "UTF8", "string": "additional data"},
|
||||||
|
"Raw", "Hex"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "Ascon Decrypt",
|
||||||
|
"args": [
|
||||||
|
{"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"},
|
||||||
|
{"option": "Hex", "string": "101112131415161718191a1b1c1d1e1f"},
|
||||||
|
{"option": "UTF8", "string": "additional data"},
|
||||||
|
"Hex", "Raw"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
154
tests/operations/tests/AutomatedValidation.mjs
Normal file
154
tests/operations/tests/AutomatedValidation.mjs
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
/**
|
||||||
|
* Automated Parameter Validation tests
|
||||||
|
*
|
||||||
|
* @author CyberChef
|
||||||
|
* @copyright Crown Copyright 2026
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Automated Validation: Valid values",
|
||||||
|
input: "test",
|
||||||
|
expectedOutput: "Success",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Automated Validation Test Op",
|
||||||
|
args: [5, 1.5, "hello", "", { "option": "Option A", "string": "test" }, "Option 1"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Automated Validation: Integer Number under min limit",
|
||||||
|
input: "test",
|
||||||
|
expectedOutput: "Integer Number must be greater than or equal to 5.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Automated Validation Test Op",
|
||||||
|
args: [4, 1.5, "hello", "", { "option": "Option A", "string": "test" }, "Option 1"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Automated Validation: Integer Number over max limit",
|
||||||
|
input: "test",
|
||||||
|
expectedOutput: "Integer Number must be less than or equal to 10.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Automated Validation Test Op",
|
||||||
|
args: [11, 1.5, "hello", "", { "option": "Option A", "string": "test" }, "Option 1"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Automated Validation: Integer Number not an integer",
|
||||||
|
input: "test",
|
||||||
|
expectedOutput: "Integer Number must be an integer.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Automated Validation Test Op",
|
||||||
|
args: [5.5, 1.5, "hello", "", { "option": "Option A", "string": "test" }, "Option 1"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Automated Validation: Real Number under min limit",
|
||||||
|
input: "test",
|
||||||
|
expectedOutput: "Real Number must be greater than or equal to 1.5.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Automated Validation Test Op",
|
||||||
|
args: [5, 1.4, "hello", "", { "option": "Option A", "string": "test" }, "Option 1"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Automated Validation: Real Number over max limit",
|
||||||
|
input: "test",
|
||||||
|
expectedOutput: "Real Number must be less than or equal to 5.5.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Automated Validation Test Op",
|
||||||
|
args: [5, 5.6, "hello", "", { "option": "Option A", "string": "test" }, "Option 1"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Automated Validation: Non Empty String over maxLength limit",
|
||||||
|
input: "test",
|
||||||
|
expectedOutput: "Non Empty String length cannot exceed 5.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Automated Validation Test Op",
|
||||||
|
args: [5, 1.5, "helloooo", "", { "option": "Option A", "string": "test" }, "Option 1"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Automated Validation: Non Empty String is empty",
|
||||||
|
input: "test",
|
||||||
|
expectedOutput: "Non Empty String cannot be empty.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Automated Validation Test Op",
|
||||||
|
args: [5, 1.5, "", "", { "option": "Option A", "string": "test" }, "Option 1"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Automated Validation: Empty Allowed String is empty (allowed)",
|
||||||
|
input: "test",
|
||||||
|
expectedOutput: "Success",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Automated Validation Test Op",
|
||||||
|
args: [5, 1.5, "hello", "", { "option": "Option A", "string": "test" }, "Option 1"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Automated Validation: Non Empty Toggle String is empty",
|
||||||
|
input: "test",
|
||||||
|
expectedOutput: "Non Empty Toggle String cannot be empty.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Automated Validation Test Op",
|
||||||
|
args: [5, 1.5, "hello", "", { "option": "Option A", "string": "" }, "Option 1"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Automated Validation: Invalid Option value",
|
||||||
|
input: "test",
|
||||||
|
expectedOutput: "Option Ingredient must be one of the following: Option 1, Option 2, Option 3.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Automated Validation Test Op",
|
||||||
|
args: [5, 1.5, "hello", "", { "option": "Option A", "string": "test" }, "Option 4"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Automated Validation: Option value as optgroup heading (invalid)",
|
||||||
|
input: "test",
|
||||||
|
expectedOutput: "Option Ingredient must be one of the following: Option 1, Option 2, Option 3.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Automated Validation Test Op",
|
||||||
|
args: [5, 1.5, "hello", "", { "option": "Option A", "string": "test" }, "[Group 1]"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Automated Validation: Option value empty (invalid)",
|
||||||
|
input: "test",
|
||||||
|
expectedOutput: "Option Ingredient cannot be empty.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Automated Validation Test Op",
|
||||||
|
args: [5, 1.5, "hello", "", { "option": "Option A", "string": "test" }, ""]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]);
|
||||||
@ -69,5 +69,43 @@ TestRegister.addTests([
|
|||||||
{ "op": "BLAKE3",
|
{ "op": "BLAKE3",
|
||||||
"args": [16390, "ThiskeyisexactlythirtytwoBytesLo"] }
|
"args": [16390, "ThiskeyisexactlythirtytwoBytesLo"] }
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
// test vectors from https://github.com/BLAKE3-team/BLAKE3/blob/master/test_vectors/test_vectors.json
|
||||||
|
{
|
||||||
|
name: "BLAKE3: Std test vector - 0 bytes input, plain hash",
|
||||||
|
input: "",
|
||||||
|
expectedOutput: "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262e00f03e7b69af26b7faaf09fcd333050338ddfe085b8cc869ca98b206c08243a26f5487789e8f660afe6c99ef9e0c52b92e7393024a80459cf91f476f9ffdbda7001c22e159b402631f277ca96f2defdf1078282314e763699a31c5363165421cce14d",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "BLAKE3",
|
||||||
|
"args": [131, ""]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "BLAKE3: Std test vector - 0 bytes input, keyed hash",
|
||||||
|
input: "",
|
||||||
|
expectedOutput: "92b2b75604ed3c761f9d6f62392c8a9227ad0ea3f09573e783f1498a4ed60d26b18171a2f22a4b94822c701f107153dba24918c4bae4d2945c20ece13387627d3b73cbf97b797d5e59948c7ef788f54372df45e45e4293c7dc18c1d41144a9758be58960856be1eabbe22c2653190de560ca3b2ac4aa692a9210694254c371e851bc8f",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "BLAKE3",
|
||||||
|
"args": [131, "whats the Elvish word for friend"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "BLAKE3: Std test vector - 7 bytes input, keyed hash",
|
||||||
|
input: "0001020304050607",
|
||||||
|
expectedOutput: "be2f5495c61cba1bb348a34948c004045e3bd4dae8f0fe82bf44d0da245a060048eb5e68ce6dea1eb0229e144f578b3aa7e9f4f85febd135df8525e6fe40c6f0340d13dd09b255ccd5112a94238f2be3c0b5b7ecde06580426a93e0708555a265305abf86d874e34b4995b788e37a823491f25127a502fe0704baa6bfdf04e76c13276",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "From Hex",
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "BLAKE3",
|
||||||
|
"args": [131, "whats the Elvish word for friend"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -172,5 +172,27 @@ TestRegister.addTests([
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "To Base32: should support non-BMP Unicode alphabets",
|
||||||
|
input: "hello",
|
||||||
|
expectedOutput: "🀝🀈🀐🀔🀖🀀🀊🀟",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "To Base32",
|
||||||
|
args: ["🀇🀈🀉🀊🀋🀌🀍🀎🀏🀙🀚🀛🀜🀝🀞🀟🀠🀡🀐🀑🀒🀓🀔🀕🀖🀗🀘🀀🀁🀂🀃🀅"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "To Base32: should omit padding for 32-character Unicode alphabets",
|
||||||
|
input: "hell",
|
||||||
|
expectedOutput: "🀝🀈🀐🀔🀖🀀🀇",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "To Base32",
|
||||||
|
args: ["🀇🀈🀉🀊🀋🀌🀍🀎🀏🀙🀚🀛🀜🀝🀞🀟🀠🀡🀐🀑🀒🀓🀔🀕🀖🀗🀘🀀🀁🀂🀃🀅"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@ -71,7 +71,7 @@ TestRegister.addTests([
|
|||||||
{
|
{
|
||||||
name: "Encode text: empty encoding",
|
name: "Encode text: empty encoding",
|
||||||
input: "hello",
|
input: "hello",
|
||||||
expectedOutput: "Invalid encoding",
|
expectedOutput: "Encoding cannot be empty.",
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
"op": "Encode text",
|
"op": "Encode text",
|
||||||
@ -82,7 +82,7 @@ TestRegister.addTests([
|
|||||||
{
|
{
|
||||||
name: "Decode text: empty encoding",
|
name: "Decode text: empty encoding",
|
||||||
input: "68 65 6c 6c 6f",
|
input: "68 65 6c 6c 6f",
|
||||||
expectedOutput: "Invalid encoding",
|
expectedOutput: "Encoding cannot be empty.",
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
"op": "From Hex",
|
"op": "From Hex",
|
||||||
|
|||||||
66
tests/operations/tests/DechunkHTTPResponse.mjs
Normal file
66
tests/operations/tests/DechunkHTTPResponse.mjs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* DechunkHTTPResponse operation tests.
|
||||||
|
*
|
||||||
|
* @author Willi Ballenthin
|
||||||
|
* @copyright Crown Copyright 2026
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Dechunk HTTP response: CRLF line endings",
|
||||||
|
input: "7\r\nMozilla\r\n9\r\nDeveloper\r\n7\r\nNetwork\r\n0\r\n\r\n",
|
||||||
|
expectedOutput: "MozillaDeveloperNetwork",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Dechunk HTTP response",
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Dechunk HTTP response: LF line endings",
|
||||||
|
input: "7\nMozilla\n9\nDeveloper\n7\nNetwork\n0\n\n",
|
||||||
|
expectedOutput: "MozillaDeveloperNetwork",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Dechunk HTTP response",
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Dechunk HTTP response: single chunk",
|
||||||
|
input: "5\r\nHello\r\n0\r\n\r\n",
|
||||||
|
expectedOutput: "Hello",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Dechunk HTTP response",
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Dechunk HTTP response: trailing headers discarded",
|
||||||
|
input: "7\nMozilla\n9\nDeveloper\n7\nNetwork\n0\nExpires: Wed, 21 Oct 2015 07:28:00 GMT\n",
|
||||||
|
expectedOutput: "MozillaDeveloperNetwork",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Dechunk HTTP response",
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Dechunk HTTP response: hex chunk sizes",
|
||||||
|
input: "a\r\n0123456789\r\n0\r\n\r\n",
|
||||||
|
expectedOutput: "0123456789",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Dechunk HTTP response",
|
||||||
|
args: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
@ -14,15 +14,18 @@ const validTokenSha256 = "eyJyb2xlIjoic3VwZXJ1c2VyIiwidXNlciI6ImFkbWluIn0.aab3Ew
|
|||||||
const validKey = "mysecretkey";
|
const validKey = "mysecretkey";
|
||||||
const wrongKey = "notTheKey";
|
const wrongKey = "notTheKey";
|
||||||
|
|
||||||
const outputObject = {
|
const outputObject = `{
|
||||||
user: "admin",
|
"role": "superuser",
|
||||||
role: "superuser",
|
"user": "admin"
|
||||||
};
|
}`;
|
||||||
|
|
||||||
const outputVerify = {
|
const outputVerify = `{
|
||||||
valid: true,
|
"valid": true,
|
||||||
payload: outputObject,
|
"payload": {
|
||||||
};
|
"role": "superuser",
|
||||||
|
"user": "admin"
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
TestRegister.addTests([
|
TestRegister.addTests([
|
||||||
{
|
{
|
||||||
|
|||||||
66
tests/operations/tests/FromBase.mjs
Normal file
66
tests/operations/tests/FromBase.mjs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* From Base operation tests.
|
||||||
|
*
|
||||||
|
* @author Willi Ballenthin
|
||||||
|
* @copyright Crown Copyright 2026
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "From Base: binary integer",
|
||||||
|
input: "1010",
|
||||||
|
expectedOutput: "10",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "From Base",
|
||||||
|
args: [2],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "From Base: binary fraction",
|
||||||
|
input: "10.1",
|
||||||
|
expectedOutput: "2.5",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "From Base",
|
||||||
|
args: [2],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "From Base: hex fraction",
|
||||||
|
input: "a.8",
|
||||||
|
expectedOutput: "10.5",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "From Base",
|
||||||
|
args: [16],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "From Base: octal integer",
|
||||||
|
input: "77",
|
||||||
|
expectedOutput: "63",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "From Base",
|
||||||
|
args: [8],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "From Base: octal fraction",
|
||||||
|
input: "7.4",
|
||||||
|
expectedOutput: "7.5",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "From Base",
|
||||||
|
args: [8],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
80
tests/operations/tests/GenerateLoremIpsum.mjs
Normal file
80
tests/operations/tests/GenerateLoremIpsum.mjs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* Generate Lorem Ipsum tests
|
||||||
|
*
|
||||||
|
* @author GCHQDeveloper581
|
||||||
|
* @copyright Crown Copyright 2025
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Generate Lorem Ipsum: Exceeds Word Limit",
|
||||||
|
input: "",
|
||||||
|
expectedOutput: "Length must be less than 100000",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Generate Lorem Ipsum",
|
||||||
|
"args": [999_999, "Words"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Generate Lorem Ipsum: Within Word Limit",
|
||||||
|
input: "",
|
||||||
|
// each word is >= 3 characters long, so expect at least 3000 characters
|
||||||
|
expectedMatch: /.{3000,}/s,
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Generate Lorem Ipsum",
|
||||||
|
"args": [1000, "Words"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Generate Lorem Ipsum: Exceeds Byte Limit",
|
||||||
|
input: "",
|
||||||
|
expectedOutput: "Length must be less than 1000000",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Generate Lorem Ipsum",
|
||||||
|
"args": [1_000_001, "Bytes"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Generate Lorem Ipsum: Exceeds Sentence Limit",
|
||||||
|
input: "",
|
||||||
|
expectedOutput: "Length must be less than 100000",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Generate Lorem Ipsum",
|
||||||
|
"args": [999_999, "Sentences"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Generate Lorem Ipsum: Exceeds Paragraph Limit",
|
||||||
|
input: "",
|
||||||
|
expectedOutput: "Length must be less than 100000",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Generate Lorem Ipsum",
|
||||||
|
"args": [999_999, "Paragraphs"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Generate Lorem Ipsum: Incorrect lengthType",
|
||||||
|
input: "",
|
||||||
|
expectedOutput: "Length in must be one of the following: Paragraphs, Sentences, Words, Bytes.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Generate Lorem Ipsum",
|
||||||
|
"args": [999_999, "Novels"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
]);
|
||||||
@ -86,4 +86,64 @@ TestRegister.addTests([
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Gzip: Comment with checksum round-trips through Gunzip",
|
||||||
|
input: "hello hello hello",
|
||||||
|
expectedOutput: "hello hello hello",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Gzip",
|
||||||
|
args: ["Dynamic Huffman Coding", "", "test", true]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
op: "Gunzip",
|
||||||
|
args: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Gzip: Filename and comment with checksum round-trips through Gunzip",
|
||||||
|
input: "The quick brown fox jumped over the slow dog",
|
||||||
|
expectedOutput: "The quick brown fox jumped over the slow dog",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Gzip",
|
||||||
|
args: ["Dynamic Huffman Coding", "file.txt", "a comment", true]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
op: "Gunzip",
|
||||||
|
args: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Gzip: No comment, with checksum round-trips through Gunzip",
|
||||||
|
input: "The quick brown fox jumped over the slow dog",
|
||||||
|
expectedOutput: "The quick brown fox jumped over the slow dog",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Gzip",
|
||||||
|
args: ["Dynamic Huffman Coding", "", "", true]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
op: "Gunzip",
|
||||||
|
args: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Gzip: No options round-trips through Gunzip",
|
||||||
|
input: "The quick brown fox jumped over the slow dog",
|
||||||
|
expectedOutput: "The quick brown fox jumped over the slow dog",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Gzip",
|
||||||
|
args: ["Dynamic Huffman Coding", "", "", false]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
op: "Gunzip",
|
||||||
|
args: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -993,6 +993,17 @@ TestRegister.addTests([
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Bcrypt compare: invalid salt version",
|
||||||
|
input: "password",
|
||||||
|
expectedOutput: "Error: Invalid salt version: $a",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Bcrypt compare",
|
||||||
|
args: ["$ab$04$K.H1WlFDQ/iIo/PiprT/puwluJ5rzuSE5q8D/Fk3NuLgU2aXiGR9m"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Scrypt: RFC test vector 1",
|
name: "Scrypt: RFC test vector 1",
|
||||||
input: "",
|
input: "",
|
||||||
|
|||||||
@ -126,6 +126,17 @@ TestRegister.addTests([
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "To Hexdump: Width too large",
|
||||||
|
input: "H",
|
||||||
|
expectedOutput: "Width must be less than or equal to 65536.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "To Hexdump",
|
||||||
|
args: [155555555555555, false, false, false]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "From Hexdump: xxd",
|
name: "From Hexdump: xxd",
|
||||||
input: `00000000: 0001 0203 0405 0607 0809 0a0b 0c0d 0e0f ................
|
input: `00000000: 0001 0203 0405 0607 0809 0a0b 0c0d 0e0f ................
|
||||||
|
|||||||
@ -12,7 +12,7 @@ TestRegister.addTests([
|
|||||||
{
|
{
|
||||||
name: "IPv6 Transition: IPv4 to IPv6",
|
name: "IPv6 Transition: IPv4 to IPv6",
|
||||||
input: "198.51.100.7",
|
input: "198.51.100.7",
|
||||||
expectedOutput: "6to4: 2002:c633:6407::/48\nIPv4 Mapped: ::ffff:c633:6407\nIPv4 Translated: ::ffff:0:c633:6407\nNat 64: 64:ff9b::c633:6407",
|
expectedOutput: "6to4: 2002:c633:6407::/48\nIPv4 Mapped: ::ffff:c633:6407\nIPv4 Translated: ::ffff:0:c633:6407\nNat 64: 64:ff9b::c633:6407\n",
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
op: "IPv6 Transition Addresses",
|
op: "IPv6 Transition Addresses",
|
||||||
@ -22,7 +22,7 @@ TestRegister.addTests([
|
|||||||
}, {
|
}, {
|
||||||
name: "IPv6 Transition: IPv4 /24 Range to IPv6",
|
name: "IPv6 Transition: IPv4 /24 Range to IPv6",
|
||||||
input: "198.51.100.0/24",
|
input: "198.51.100.0/24",
|
||||||
expectedOutput: "6to4: 2002:c633:6400::/40\nIPv4 Mapped: ::ffff:c633:6400/120\nIPv4 Translated: ::ffff:0:c633:6400/120\nNat 64: 64:ff9b::c633:6400/120",
|
expectedOutput: "6to4: 2002:c633:6400::/40\nIPv4 Mapped: ::ffff:c633:6400/120\nIPv4 Translated: ::ffff:0:c633:6400/120\nNat 64: 64:ff9b::c633:6400/120\n",
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
op: "IPv6 Transition Addresses",
|
op: "IPv6 Transition Addresses",
|
||||||
@ -32,7 +32,7 @@ TestRegister.addTests([
|
|||||||
}, {
|
}, {
|
||||||
name: "IPv6 Transition: IPv4 to IPv6 Remove headers",
|
name: "IPv6 Transition: IPv4 to IPv6 Remove headers",
|
||||||
input: "198.51.100.7",
|
input: "198.51.100.7",
|
||||||
expectedOutput: "2002:c633:6407::/48\n::ffff:c633:6407\n::ffff:0:c633:6407\n64:ff9b::c633:6407",
|
expectedOutput: "2002:c633:6407::/48\n::ffff:c633:6407\n::ffff:0:c633:6407\n64:ff9b::c633:6407\n",
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
op: "IPv6 Transition Addresses",
|
op: "IPv6 Transition Addresses",
|
||||||
@ -42,7 +42,7 @@ TestRegister.addTests([
|
|||||||
}, {
|
}, {
|
||||||
name: "IPv6 Transition: IPv6 to IPv4",
|
name: "IPv6 Transition: IPv6 to IPv4",
|
||||||
input: "64:ff9b::c633:6407",
|
input: "64:ff9b::c633:6407",
|
||||||
expectedOutput: "IPv4: 198.51.100.7",
|
expectedOutput: "IPv4: 198.51.100.7\n",
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
op: "IPv6 Transition Addresses",
|
op: "IPv6 Transition Addresses",
|
||||||
|
|||||||
@ -45,6 +45,17 @@ TestRegister.addTests([
|
|||||||
{ op: "Render Image", args: ["Base64"] }
|
{ op: "Render Image", args: ["Base64"] }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Generate Image: empty mode",
|
||||||
|
input: "",
|
||||||
|
expectedOutput: "Mode cannot be empty.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Generate Image",
|
||||||
|
args: ["", 8, 64]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Extract EXIF: nothing",
|
name: "Extract EXIF: nothing",
|
||||||
input: "",
|
input: "",
|
||||||
@ -230,6 +241,21 @@ TestRegister.addTests([
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "View Bit Plane: malformed PNG",
|
||||||
|
input: PNG_HEX.replace("49484452", "49424452"),
|
||||||
|
expectedOutput: "Error loading image. (Error: unrecognised content at end of stream)",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "From Hex",
|
||||||
|
args: ["None"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
op: "View Bit Plane",
|
||||||
|
args: ["Red", 0]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Randomize Colour Palette",
|
name: "Randomize Colour Palette",
|
||||||
"input": PNG_HEX,
|
"input": PNG_HEX,
|
||||||
|
|||||||
@ -548,4 +548,27 @@ TestRegister.addTests([
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
// Base64 functions (issue #2063)
|
||||||
|
{
|
||||||
|
name: "Jsonata: $base64decode",
|
||||||
|
input: "{}",
|
||||||
|
expectedOutput: '"Hello World!"',
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Jsonata Query",
|
||||||
|
args: ['$base64decode("SGVsbG8gV29ybGQh")'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Jsonata: $base64encode",
|
||||||
|
input: "{}",
|
||||||
|
expectedOutput: '"SGVsbG8gV29ybGQh"',
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Jsonata Query",
|
||||||
|
args: ['$base64encode("Hello World!")'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -75,6 +75,39 @@ TestRegister.addTests([
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "UTF-8 Base64 non-ASCII",
|
||||||
|
input: "Subject: =?UTF-8?B?Y2Fmw6k=?=",
|
||||||
|
expectedOutput: "Subject: café",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "MIME Decoding",
|
||||||
|
"args": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "UTF-8 Base64 multibyte CJK",
|
||||||
|
input: "Subject: =?UTF-8?B?5pel5pys6Kqe?=",
|
||||||
|
expectedOutput: "Subject: 日本語",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "MIME Decoding",
|
||||||
|
"args": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "UTF-8 Base64 ASCII-only",
|
||||||
|
input: "Subject: =?UTF-8?B?aGVsbG8=?=",
|
||||||
|
expectedOutput: "Subject: hello",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "MIME Decoding",
|
||||||
|
"args": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "ISO Decoding",
|
name: "ISO Decoding",
|
||||||
input: "From: =?US-ASCII?Q?Keith_Moore?= <moore@cs.utk.edu>\nTo: =?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= <keld@dkuug.dk>\nCC: =?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>\nSubject: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=\n=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=",
|
input: "From: =?US-ASCII?Q?Keith_Moore?= <moore@cs.utk.edu>\nTo: =?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= <keld@dkuug.dk>\nCC: =?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>\nSubject: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=\n=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=",
|
||||||
|
|||||||
33
tests/operations/tests/Median.mjs
Normal file
33
tests/operations/tests/Median.mjs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Median operation tests.
|
||||||
|
*
|
||||||
|
* @author copilot-swe-agent[bot]
|
||||||
|
* @copyright Crown Copyright 2018
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Median: odd-length input",
|
||||||
|
input: "10 1 2",
|
||||||
|
expectedOutput: "2",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Median",
|
||||||
|
args: ["Space"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Median: even-length input",
|
||||||
|
input: "10 1 2 5",
|
||||||
|
expectedOutput: "3.5",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Median",
|
||||||
|
args: ["Space"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
@ -12,11 +12,176 @@ TestRegister.addTests([
|
|||||||
{
|
{
|
||||||
name: "Generate HOTP",
|
name: "Generate HOTP",
|
||||||
input: "JBSWY3DPEHPK3PXP",
|
input: "JBSWY3DPEHPK3PXP",
|
||||||
expectedOutput: `URI: otpauth://hotp/?secret=JBSWY3DPEHPK3PXP&algorithm=SHA1&digits=6&counter=0\n\nPassword: 282760`,
|
expectedOutput: `URI: otpauth://hotp/Account?secret=JBSWY3DPEHPK3PXP&algorithm=SHA1&digits=6&counter=0\n\nPassword: 282760`,
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
op: "Generate HOTP",
|
op: "Generate HOTP",
|
||||||
args: ["", 6, 0], // [Name, Code length, Counter]
|
args: ["Account", 6, 0], // [Name, Code length, Counter]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Generate HOTP - empty name rejected",
|
||||||
|
input: "JBSWY3DPEHPK3PXP",
|
||||||
|
expectedOutput: "Name cannot be empty.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Generate HOTP",
|
||||||
|
args: ["", 6, 0],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Generate HOTP - code length below minimum rejected",
|
||||||
|
input: "JBSWY3DPEHPK3PXP",
|
||||||
|
expectedOutput: "Code length must be greater than or equal to 6.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Generate HOTP",
|
||||||
|
args: ["Account", -6, 0],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Generate HOTP - code length above maximum rejected",
|
||||||
|
input: "JBSWY3DPEHPK3PXP",
|
||||||
|
expectedOutput: "Code length must be less than or equal to 8.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Generate HOTP",
|
||||||
|
args: ["Account", 9, 0],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Generate HOTP - non-integer code length rejected",
|
||||||
|
input: "JBSWY3DPEHPK3PXP",
|
||||||
|
expectedOutput: "Code length must be an integer.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Generate HOTP",
|
||||||
|
args: ["Account", 6.5, 0],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Generate HOTP - negative counter rejected",
|
||||||
|
input: "JBSWY3DPEHPK3PXP",
|
||||||
|
expectedOutput: "Counter must be greater than or equal to 0.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Generate HOTP",
|
||||||
|
args: ["Account", 6, -1],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Generate HOTP - special characters in name are URI-encoded",
|
||||||
|
input: "JBSWY3DPEHPK3PXP",
|
||||||
|
expectedOutput: `URI: otpauth://hotp/user%40example.com?secret=JBSWY3DPEHPK3PXP&algorithm=SHA1&digits=6&counter=0\n\nPassword: 282760`,
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Generate HOTP",
|
||||||
|
args: ["user@example.com", 6, 0],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Generate TOTP",
|
||||||
|
input: "JBSWY3DPEHPK3PXP",
|
||||||
|
expectedMatch: /^URI: otpauth:\/\/totp\/Account\?secret=JBSWY3DPEHPK3PXP&algorithm=SHA1&digits=6&period=30\n\nPassword: \d{6}$/,
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Generate TOTP",
|
||||||
|
args: ["Account", 6, 0, 30], // [Name, Code length, Epoch offset (T0), Interval (T1)]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Generate TOTP - empty name rejected",
|
||||||
|
input: "JBSWY3DPEHPK3PXP",
|
||||||
|
expectedOutput: "Name cannot be empty.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Generate TOTP",
|
||||||
|
args: ["", 6, 0, 30],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Generate TOTP - code length below minimum rejected",
|
||||||
|
input: "JBSWY3DPEHPK3PXP",
|
||||||
|
expectedOutput: "Code length must be greater than or equal to 6.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Generate TOTP",
|
||||||
|
args: ["Account", -6, 0, 30],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Generate TOTP - code length above maximum rejected",
|
||||||
|
input: "JBSWY3DPEHPK3PXP",
|
||||||
|
expectedOutput: "Code length must be less than or equal to 8.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Generate TOTP",
|
||||||
|
args: ["Account", 9, 0, 30],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Generate TOTP - non-integer code length rejected",
|
||||||
|
input: "JBSWY3DPEHPK3PXP",
|
||||||
|
expectedOutput: "Code length must be an integer.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Generate TOTP",
|
||||||
|
args: ["Account", 6.5, 0, 30],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Generate TOTP - negative interval rejected",
|
||||||
|
input: "JBSWY3DPEHPK3PXP",
|
||||||
|
expectedOutput: "Interval (T1) must be greater than or equal to 1.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Generate TOTP",
|
||||||
|
args: ["Account", 6, 0, -1],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Generate TOTP - negative epoch offset rejected",
|
||||||
|
input: "JBSWY3DPEHPK3PXP",
|
||||||
|
expectedOutput: "Epoch offset (T0) must be greater than or equal to 0.",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Generate TOTP",
|
||||||
|
args: ["Account", 6, -1, 30],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Generate HOTP - invalid base32 secret rejected",
|
||||||
|
input: "not,valid|base32;input",
|
||||||
|
expectedOutput: "Invalid secret. The input must be a valid base32 string (characters A–Z and 2–7).",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Generate HOTP",
|
||||||
|
args: ["Account", 6, 0],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Generate TOTP - invalid base32 secret rejected",
|
||||||
|
input: "not,valid|base32;input",
|
||||||
|
expectedOutput: "Invalid secret. The input must be a valid base32 string (characters A–Z and 2–7).",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Generate TOTP",
|
||||||
|
args: ["Account", 6, 0, 30],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user