diff --git a/.editorconfig b/.editorconfig index b50059bbdd..cef4cab075 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,3 +12,7 @@ indent_size = 4 [{package.json,.travis.yml,nightwatch.json}] indent_style = space indent_size = 2 + +[.github/**.yml] +indent_style = space +indent_size = 2 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 1350e97697..0000000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: "CodeQL Analysis" - -on: - workflow_dispatch: - push: - branches: [ master ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ master ] - types: [synchronize, opened, reopened] - schedule: - - cron: '22 17 * * 5' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'javascript' ] - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 - with: - category: "/language:${{matrix.language}}" diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 8a3aff54b3..74710dff16 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -1,58 +1,64 @@ name: "Master Build, Test & Deploy" +permissions: + contents: read + on: workflow_dispatch: push: branches: - - master + - master jobs: main: + permissions: + contents: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - - name: Set node version - uses: actions/setup-node@v3 - with: - node-version: '18.x' - - - name: Install - run: | - export DETECT_CHROMEDRIVER_VERSION=true - npm install - npm run setheapsize - - - name: Lint - run: npx grunt lint - - - name: Unit Tests - run: | - npm test - npm run testnodeconsumer - - - name: Production Build - if: success() - run: npx grunt prod --msg="Version 10 is here! Read about the new features here" - - - name: Generate sitemap - run: npx grunt exec:sitemap - - - name: UI Tests - if: success() - run: | - sudo apt-get install xvfb - xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui - - - name: Prepare for GitHub Pages - if: success() - run: npx grunt copy:ghPages - - - name: Deploy to GitHub Pages - if: success() && github.ref == 'refs/heads/master' - uses: crazy-max/ghaction-github-pages@v3 - with: - target_branch: gh-pages - build_dir: ./build/prod - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/checkout@v6 + + - name: Set node version + uses: actions/setup-node@v6 + with: + node-version: 18 + registry-url: "https://registry.npmjs.org" + + - name: Install + run: | + export DETECT_CHROMEDRIVER_VERSION=true + npm install + npm run setheapsize + + - name: Lint + run: npx grunt lint + + - name: Unit Tests + run: | + npm test + npm run testnodeconsumer + + - name: Production Build + if: success() + run: npx grunt prod --msg="" + + - name: Generate sitemap + run: npx grunt exec:sitemap + + - name: UI Tests + if: success() + run: | + sudo apt-get install xvfb + xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui + + - name: Prepare for GitHub Pages + if: success() + run: npx grunt copy:ghPages + + - name: Deploy to GitHub Pages + if: success() && github.ref == 'refs/heads/master' + uses: crazy-max/ghaction-github-pages@v3 + with: + target_branch: gh-pages + build_dir: ./build/prod + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index 296e60b99a..8f04df72c9 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -1,5 +1,8 @@ name: "Pull Requests" +permissions: + contents: read + on: workflow_dispatch: pull_request: @@ -9,47 +12,46 @@ jobs: main: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - - name: Set node version - uses: actions/setup-node@v3 - with: - node-version: '18.x' - - - name: Install - run: | - export DETECT_CHROMEDRIVER_VERSION=true - npm install - npm run setheapsize - - - name: Lint - run: npx grunt lint - - - name: Unit Tests - run: | - npm test - npm run testnodeconsumer - - - name: Production Build - if: success() - run: npx grunt prod - - - name: Production Image Build - if: success() - id: build-image - uses: redhat-actions/buildah-build@v2 - with: - # Not being uploaded to any registry, use a simple name to allow Buildah to build correctly. - image: cyberchef - containerfiles: ./Dockerfile - platforms: linux/amd64 - oci: true - # Webpack seems to use a lot of open files, increase the max open file limit to accomodate. - extra-args: | - --ulimit nofile=10000 - - - name: UI Tests - if: success() - run: | - sudo apt-get install xvfb - xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui + - uses: actions/checkout@v6 + + - name: Set node version + uses: actions/setup-node@v6 + with: + node-version: 18 + registry-url: "https://registry.npmjs.org" + + - name: Install + run: | + export DETECT_CHROMEDRIVER_VERSION=true + npm install + npm run setheapsize + + - name: Lint + run: npx grunt lint + + - name: Unit Tests + run: | + npm test + npm run testnodeconsumer + + - name: Production Build + if: success() + run: npx grunt prod + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Production Image Build + if: success() + id: build-image + uses: docker/build-push-action@v6 + with: + platforms: linux/amd64,linux/arm64 + - name: UI Tests + if: success() + run: | + sudo apt-get install xvfb + xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index 209687723d..b40af8761e 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -4,7 +4,11 @@ on: workflow_dispatch: push: tags: - - 'v*' + - "v*" + +permissions: + id-token: write + contents: read env: REGISTRY: ghcr.io @@ -16,82 +20,78 @@ jobs: main: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - - name: Set node version - uses: actions/setup-node@v3 - with: - node-version: '18.x' - - - name: Install - run: | - export DETECT_CHROMEDRIVER_VERSION=true - npm ci - npm run setheapsize - - - name: Lint - run: npx grunt lint - - - name: Unit Tests - run: | - npm test - npm run testnodeconsumer - - - name: Production Build - run: npx grunt prod - - - name: UI Tests - run: | - sudo apt-get install xvfb - xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui - - - name: Image Metadata - id: image-metadata - uses: docker/metadata-action@v4 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=semver,pattern={{major}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{version}} - - - name: Production Image Build - id: build-image - uses: redhat-actions/buildah-build@v2 - with: - tags: ${{ steps.image-metadata.outputs.tags }} - labels: ${{ steps.image-metadata.outputs.labels }} - containerfiles: ./Dockerfile - platforms: linux/amd64,linux/arm64 - oci: true - # enable build layer caching between platforms - layers: true - # Webpack seems to use a lot of open files, increase the max open file limit to accomodate. - extra-args: | - --ulimit nofile=10000 - - - name: Publish to GHCR - uses: redhat-actions/push-to-registry@v2 - with: - image: ${{ steps.build-image.outputs.image }} - tags: ${{ steps.build-image.outputs.tags }} - registry: ${{ env.REGISTRY }} - username: ${{ env.REGISTRY_USER }} - password: ${{ env.REGISTRY_PASSWORD }} - - - name: Upload Release Assets - id: upload-release-assets - uses: svenstaro/upload-release-action@v2 - with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: build/prod/*.zip - tag: ${{ github.ref }} - overwrite: true - file_glob: true - body: "See the [CHANGELOG](https://github.com/gchq/CyberChef/blob/master/CHANGELOG.md) and [commit messages](https://github.com/gchq/CyberChef/commits/master) for details." - - - name: Publish to NPM - uses: JS-DevTools/npm-publish@v1 - if: false - with: - token: ${{ secrets.NPM_TOKEN }} \ No newline at end of file + - uses: actions/checkout@v6 + + - name: Set node version + uses: actions/setup-node@v6 + with: + node-version: 18 + registry-url: "https://registry.npmjs.org" + + - name: Install + run: | + export DETECT_CHROMEDRIVER_VERSION=true + npm ci + npm run setheapsize + + - name: Lint + run: npx grunt lint + + - name: Unit Tests + run: | + npm test + npm run testnodeconsumer + + - name: Production Build + run: npx grunt prod + + - name: UI Tests + run: | + sudo apt-get install xvfb + xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Image Metadata + id: image-metadata + uses: docker/metadata-action@v4 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=semver,pattern={{major}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{version}} + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ env.REGISTRY_USER }} + password: ${{ env.REGISTRY_PASSWORD }} + + - name: Publish to GHCR + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ steps.image-metadata.outputs.tags }} + labels: ${{ steps.image-metadata.outputs.labels }} + platforms: linux/amd64,linux/arm64 + + - name: Upload Release Assets + id: upload-release-assets + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: build/prod/*.zip + tag: ${{ github.ref }} + overwrite: true + file_glob: true + body: "See the [CHANGELOG](https://github.com/gchq/CyberChef/blob/master/CHANGELOG.md) and [commit messages](https://github.com/gchq/CyberChef/commits/master) for details." + + - name: Publish to NPM + run: npm publish diff --git a/CHANGELOG.md b/CHANGELOG.md index d3a8feb709..535a11a352 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,75 @@ All major and minor version changes will be documented in this file. Details of ## Details +### [10.20.0] - 2026-01-28 +- Fixed Optical Character Recognition and added tests [@n1474335] | [ab37c1e] +- Fixed JA4 version fallback value [@n1474335] | [7a5225c] +- Updated chromedriver [@n1474335] | [0e82e4b] +- Fixed RSA Sign and Verify character encodings [@n1474335] | [895a929] +- Updated chromedriver [@n1474335] | [d3adfc7] +- Added message format arg to RSA Verify operation [@n1474335] | [47c85a1] +- Add operation for parsing X.509 CRLs [@robinsandhu] | [#1887] +- Fix typo in description of JWT Sign recipe [@GuilhermoReadonly] | [#1961] +- Corrected path to generateNodeIndex.mjs [@simonarnell] | [#1959] +- Add 'header' ingredient to JWT Sign operation [@RandomByte] | [#1957] +- Add Parse TLS record operation [@c65722] | [#1936] +- Automatically detect chrome driver version [@gchq] | [#1972] +- Add Strip UDP header operation [@c65722] | [#1900] +- Add Strip TCP header operation [@c65722] | [#1898] +- Webpack compress with gzip and brotli [@max0x53] | [#1955] +- add offset field to 'Add Line Numbers' operation [@Adamkadaban] | [#1866] +- Disable flakey URL test [@a3957273] | [#1973] +- Add Strip IPv4 header operation [@c65722] | [#1899] +- IPv6 Transition Operation [@jb30795] | [#1780] +- fix: Blowfish - ignore IV length in ECB mode [@FranciscoPombal] | [#1902] +- Add 'Drop nth bytes' operation [@Oshawk] | [#1914] +- Add 'Take nth bytes' operation [@Oshawk] | [#1915] +- Add Leet Speak [@bartblaze] | [#1971] +- Fix Generate TOTP & HOPT [@exactlyaron] | [#1966] +- Updated luhn checksum operation to work with different bases [@k3ach] | [#1933] +- automatically theme mode based on user preference [@vs4vijay] | [#1921] +- fix: DES/Triple DES - misleading error messages [@FranciscoPombal] | [#1904] +- fix: ROT13 - shifting numbers by negative amounts [@FranciscoPombal] | [#1903] +- Introduce Yubico's Modhex for Conversion [@linuxgemini] | [#1105] +- Feature: MIME RFC2047 Decoding [@MShwed] | [#630] +- CC-1889 add _ option [@depperm] | [#1977] +- chore(root): add cspell [@evenstensberg] | [#1976] +- Preserve uppercase for Leet Speak [@bartblaze] | [#1981] +- Load the user's preferred color scheme if the URL contains an invalid theme [@0xh3xa] | [#2007] +- Add SM2 Encrypt and Decrypt Operations [@flakjacket95] | [#1909] +- Support jq as an operation. [@zhzy0077] | [#1604] +- Add fingerprints to the 'Parse X.509 certificate' operation [@JSCU-CNI] | [#1863] +- Added a JSON to YAML and a YAML to JSON operation [@ccarpo] | [#1286] +- Add CRC Operation [@r4mos] | [#1993] +- Bug Fix: selected theme not loading when refreshing [@0xh3xa] | [#2006] +- Fix(RecipeWaiter): sanitize user input in addOperation to prevent XSS [@0xh3xa] | [#2014] +- Docker multiplatform build support [@PathToLife] | [#1974] +- Add Base32 Hex Extended Alphabet and Base32 Tests. [@peterc-s] | [#1991] +- Add ECB/NoPadding and CBC/NoPadding support to AES encryption [@plvie] | [#2013] +- Add new operation: PHP Serialize [@brun0ne] | [#1548] +- Push input through postmessage [@kenduguay1] | [#1992] +- Add jsonata query operation [@jonking-ajar] | [#1587] +- Re-enable Npm Release in github workflows [@PathToLife] | [#2031] +- Add to ECDSA Verify the message format [@r4mos] | [#2027] +- Added alternating caps functionality [@sw5678] | [#1897] +- XOR Checksum operation added [@jg42526] | [#2035] +- Add GenerateAllChecksums operation * Remove checksums from GenerateAllHashes operation [@es45411] | [66d445c] +- Update GenerateAllChecksums infoURL [@es45411] | [#2037] +- Add toggle "+" character to URLDecode operation [@es45411] | [#2040] +- Workaround for Safari load bug [@GCHQDeveloper94872] | [#2038] +- Updated Dockerfile to correctly build on ARM64 platforms [@Sma-Das] | [#2042] +- Addresses bug report #2008 Added explicit support for octal IP addresses. Changed approach to IPv4 regex to be string manipulation generated. Added some unit tests for IP address parsing - probably not full coverage. Added lookahead and lookbehind tricks to resolve warned issue that 1.2.3.256 would still be extracted as 1.2.3.25. Now only accepts valid IP addresses. Warning replaced with clause about infinite length dotted decimal forms. [@gchqdev364] | [#2041] +- Remove trim from rail fence [@Odyhibit] | [#1986] +- Fix email regex [@ericli-splunk] | [#2025] +- Add Blake3 hashing [@xumptex] | [#2023] +- Use defaultIndex instead of 0 in transformArgs [@bartvanandel] | [#2015] +- Add "Generate UUID" and "Analyse UUID" operations [@bartvanandel] | [#2011] +- Add new operation: Template [@kendallgoto] | [#2021] +- Add more clear build instructions [@remingtr] | [#1873] +- Show On Map updated to use leaflet over WikiMedia [@0xff1ce] | [#1884] +- Fixed ToDecimal signed logic [@starplanet] | [#1545] +- Use BigInt for encoding/decoding VarInt [@mikecat] | [#1978] + ### [10.19.0] - 2024-06-21 - Add support for ECDSA and DSA in 'Parse CSR' [@robinsandhu] | [#1828] - Fix typos in SIGABA.mjs [@eltociear] | [#1834] @@ -440,6 +509,7 @@ All major and minor version changes will be documented in this file. Details of ## [4.0.0] - 2016-11-28 - Initial open source commit [@n1474335] | [b1d73a72](https://github.com/gchq/CyberChef/commit/b1d73a725dc7ab9fb7eb789296efd2b7e4b08306) +[10.20.0]: https://github.com/gchq/CyberChef/releases/tag/v10.20.0 [10.19.0]: https://github.com/gchq/CyberChef/releases/tag/v10.19.0 [10.18.0]: https://github.com/gchq/CyberChef/releases/tag/v10.18.0 [10.17.0]: https://github.com/gchq/CyberChef/releases/tag/v10.17.0 @@ -630,6 +700,60 @@ All major and minor version changes will be documented in this file. Details of [@cplussharp]: https://github.com/cplussharp [@robinsandhu]: https://github.com/robinsandhu [@eltociear]: https://github.com/eltociear +[@GuilhermoReadonly]: https://github.com/GuilhermoReadonly +[@simonarnell]: https://github.com/simonarnell +[@RandomByte]: https://github.com/RandomByte +[@c65722]: https://github.com/c65722 +[@c65722]: https://github.com/c65722 +[@c65722]: https://github.com/c65722 +[@max0x53]: https://github.com/max0x53 +[@Adamkadaban]: https://github.com/Adamkadaban +[@c65722]: https://github.com/c65722 +[@jb30795]: https://github.com/jb30795 +[@FranciscoPombal]: https://github.com/FranciscoPombal +[@Oshawk]: https://github.com/Oshawk +[@Oshawk]: https://github.com/Oshawk +[@bartblaze]: https://github.com/bartblaze +[@exactlyaron]: https://github.com/exactlyaron +[@k3ach]: https://github.com/k3ach +[@vs4vijay]: https://github.com/vs4vijay +[@FranciscoPombal]: https://github.com/FranciscoPombal +[@FranciscoPombal]: https://github.com/FranciscoPombal +[@linuxgemini]: https://github.com/linuxgemini +[@depperm]: https://github.com/depperm +[@evenstensberg]: https://github.com/evenstensberg +[@bartblaze]: https://github.com/bartblaze +[@0xh3xa]: https://github.com/0xh3xa +[@flakjacket95]: https://github.com/flakjacket95 +[@zhzy0077]: https://github.com/zhzy0077 +[@JSCU-CNI]: https://github.com/JSCU-CNI +[@ccarpo]: https://github.com/ccarpo +[@r4mos]: https://github.com/r4mos +[@0xh3xa]: https://github.com/0xh3xa +[@0xh3xa]: https://github.com/0xh3xa +[@PathToLife]: https://github.com/PathToLife +[@peterc-s]: https://github.com/peterc-s +[@plvie]: https://github.com/plvie +[@kenduguay1]: https://github.com/kenduguay1 +[@jonking-ajar]: https://github.com/jonking-ajar +[@PathToLife]: https://github.com/PathToLife +[@r4mos]: https://github.com/r4mos +[@jg42526]: https://github.com/jg42526 +[@es45411]: https://github.com/es45411 +[@gchq]: https://github.com/gchq +[@gchqdev364]: https://github.com/gchqdev364 +[@GCHQDeveloper94872]: https://github.com/GCHQDeveloper94872 +[@Sma-Das]: https://github.com/Sma-Das +[@gchq]: https://github.com/gchq +[@Odyhibit]: https://github.com/Odyhibit +[@ericli-splunk]: https://github.com/ericli-splunk +[@xumptex]: https://github.com/xumptex +[@bartvanandel]: https://github.com/bartvanandel +[@bartvanandel]: https://github.com/bartvanandel +[@kendallgoto]: https://github.com/kendallgoto +[@remingtr]: https://github.com/remingtr +[@0xff1ce]: https://github.com/0xff1ce +[@starplanet]: https://github.com/starplanet [8ad18b]: https://github.com/gchq/CyberChef/commit/8ad18bc7db6d9ff184ba3518686293a7685bf7b7 @@ -642,6 +766,46 @@ All major and minor version changes will be documented in this file. Details of [760eff4]: https://github.com/gchq/CyberChef/commit/760eff49b5307aaa3104c5e5b437ffe62299acd1 [65ffd8d]: https://github.com/gchq/CyberChef/commit/65ffd8d65d88eb369f6f61a5d1d0f807179bffb7 [0a353ee]: https://github.com/gchq/CyberChef/commit/0a353eeb378b9ca5d49e23c7dfc175ae07107b08 +[66d445c]: https://github.com/gchq/CyberChef/commit/66d445c5ef4e8bd896fd15396e3ce2d660d8ace1 +[ab37c1e]: https://github.com/gchq/CyberChef/commit/ab37c1e562dbee0495ed32876ecbb8225282af25 +[965570d]: https://github.com/gchq/CyberChef/commit/965570d2504c17ee1f96211a1dc10ed40cd2b332 +[a477f47]: https://github.com/gchq/CyberChef/commit/a477f47aecd01d78b11fe186ed4b20d9c487cfac +[7a5225c]: https://github.com/gchq/CyberChef/commit/7a5225c961a5e0d192b03152117cd10a761f73d6 +[5f88ae4]: https://github.com/gchq/CyberChef/commit/5f88ae44ec77228d9bed8f11e8cc8e7dcfb36914 +[0e82e4b]: https://github.com/gchq/CyberChef/commit/0e82e4b7c6c77cadb8be61cb145e081d6ecfdc88 +[d635cca]: https://github.com/gchq/CyberChef/commit/d635cca2106aae2a59caf0e5d7e3633ee1ea3155 +[895a929]: https://github.com/gchq/CyberChef/commit/895a9299255525cb57886deb9d9fd4ba17ae9548 +[270a333]: https://github.com/gchq/CyberChef/commit/270a33317944612d27ea1cc15275ad6b0ed097e5 +[d3adfc7]: https://github.com/gchq/CyberChef/commit/d3adfc7c3e5719279524356bce5261bd8350c0f8 +[47c85a1]: https://github.com/gchq/CyberChef/commit/47c85a105ddbdd4cabfa44ddddbc56e3907a8c33 +[3822c6c]: https://github.com/gchq/CyberChef/commit/3822c6c520a0b4200abc675c33f46082f5b9efc6 +[66d445c]: https://github.com/gchq/CyberChef/commit/66d445c5ef4e8bd896fd15396e3ce2d660d8ace1 +[ab37c1e]: https://github.com/gchq/CyberChef/commit/ab37c1e562dbee0495ed32876ecbb8225282af25 +[965570d]: https://github.com/gchq/CyberChef/commit/965570d2504c17ee1f96211a1dc10ed40cd2b332 +[a477f47]: https://github.com/gchq/CyberChef/commit/a477f47aecd01d78b11fe186ed4b20d9c487cfac +[7a5225c]: https://github.com/gchq/CyberChef/commit/7a5225c961a5e0d192b03152117cd10a761f73d6 +[5f88ae4]: https://github.com/gchq/CyberChef/commit/5f88ae44ec77228d9bed8f11e8cc8e7dcfb36914 +[0e82e4b]: https://github.com/gchq/CyberChef/commit/0e82e4b7c6c77cadb8be61cb145e081d6ecfdc88 +[d635cca]: https://github.com/gchq/CyberChef/commit/d635cca2106aae2a59caf0e5d7e3633ee1ea3155 +[895a929]: https://github.com/gchq/CyberChef/commit/895a9299255525cb57886deb9d9fd4ba17ae9548 +[270a333]: https://github.com/gchq/CyberChef/commit/270a33317944612d27ea1cc15275ad6b0ed097e5 +[d3adfc7]: https://github.com/gchq/CyberChef/commit/d3adfc7c3e5719279524356bce5261bd8350c0f8 +[47c85a1]: https://github.com/gchq/CyberChef/commit/47c85a105ddbdd4cabfa44ddddbc56e3907a8c33 +[3822c6c]: https://github.com/gchq/CyberChef/commit/3822c6c520a0b4200abc675c33f46082f5b9efc6 +[66d445c]: https://github.com/gchq/CyberChef/commit/66d445c5ef4e8bd896fd15396e3ce2d660d8ace1 +[ab37c1e]: https://github.com/gchq/CyberChef/commit/ab37c1e562dbee0495ed32876ecbb8225282af25 +[965570d]: https://github.com/gchq/CyberChef/commit/965570d2504c17ee1f96211a1dc10ed40cd2b332 +[a477f47]: https://github.com/gchq/CyberChef/commit/a477f47aecd01d78b11fe186ed4b20d9c487cfac +[7a5225c]: https://github.com/gchq/CyberChef/commit/7a5225c961a5e0d192b03152117cd10a761f73d6 +[5f88ae4]: https://github.com/gchq/CyberChef/commit/5f88ae44ec77228d9bed8f11e8cc8e7dcfb36914 +[0e82e4b]: https://github.com/gchq/CyberChef/commit/0e82e4b7c6c77cadb8be61cb145e081d6ecfdc88 +[d635cca]: https://github.com/gchq/CyberChef/commit/d635cca2106aae2a59caf0e5d7e3633ee1ea3155 +[895a929]: https://github.com/gchq/CyberChef/commit/895a9299255525cb57886deb9d9fd4ba17ae9548 +[270a333]: https://github.com/gchq/CyberChef/commit/270a33317944612d27ea1cc15275ad6b0ed097e5 +[d3adfc7]: https://github.com/gchq/CyberChef/commit/d3adfc7c3e5719279524356bce5261bd8350c0f8 +[47c85a1]: https://github.com/gchq/CyberChef/commit/47c85a105ddbdd4cabfa44ddddbc56e3907a8c33 +[3822c6c]: https://github.com/gchq/CyberChef/commit/3822c6c520a0b4200abc675c33f46082f5b9efc6 +[66d445c]: https://github.com/gchq/CyberChef/commit/66d445c5ef4e8bd896fd15396e3ce2d660d8ace1 [#95]: https://github.com/gchq/CyberChef/pull/299 [#173]: https://github.com/gchq/CyberChef/pull/173 @@ -778,4 +942,3 @@ All major and minor version changes will be documented in this file. Details of [#512]: https://github.com/gchq/CyberChef/issues/512 [#1732]: https://github.com/gchq/CyberChef/issues/1732 [#1789]: https://github.com/gchq/CyberChef/issues/1789 - diff --git a/Dockerfile b/Dockerfile index d63a8ca3b9..2184a2941b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,10 +27,6 @@ RUN npm run build ######################################### # Package static build files into nginx # ######################################### -# We are using Github Actions: redhat-actions/buildah-build@v2 which needs manual selection of arch in base image -# Remove TARGETARCH if docker buildx is supported in the CI release as --platform=$TARGETPLATFORM will be automatically set -ARG TARGETARCH -ARG TARGETPLATFORM -FROM ${TARGETARCH}/nginx:stable-alpine AS cyberchef +FROM nginx:stable-alpine AS cyberchef COPY --from=builder /app/build/prod /usr/share/nginx/html/ diff --git a/README.md b/README.md index 5549bda2a5..89f0371d5b 100755 --- a/README.md +++ b/README.md @@ -20,21 +20,36 @@ Cryptographic operations in CyberChef should not be relied upon to provide secur [A live demo can be found here][1] - have fun! -## Containers +## Running Locally with Docker -If you would like to try out CyberChef locally you can either build it yourself: +**Prerequisites** +- [Docker](hhttps://www.docker.com/products/docker-desktop/) + - Docker Desktop must be open and running on your machine + + +#### Option 1: Build the Docker Image Yourself + +1. Build the docker image ```bash docker build --tag cyberchef --ulimit nofile=10000 . +``` +2. Run the docker container +```bash docker run -it -p 8080:80 cyberchef ``` +3. Navigate to `http://localhost:8080` in your browser + +#### Option 2: Use the pre-built Docker Image -Or you can use our image directly: +If you prefer to skip the build process, you can use the pre-built image ```bash docker run -it -p 8080:80 ghcr.io/gchq/cyberchef:latest ``` +Just like before, navigate to `http://localhost:8080` in your browser. + This image is built and published through our [GitHub Workflows](.github/workflows/releases.yml) ## How it works diff --git a/package-lock.json b/package-lock.json index ef2da3f0fb..0b7bce146d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "10.19.4", + "version": "10.20.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "10.19.4", + "version": "10.20.0", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -31,7 +31,7 @@ "chi-squared": "^1.1.0", "codepage": "^1.15.0", "crypto-api": "^0.8.5", - "crypto-browserify": "^3.12.0", + "crypto-browserify": "^3.12.1", "crypto-js": "^4.2.0", "ctph.js": "0.0.5", "d3": "7.9.0", @@ -46,6 +46,8 @@ "file-saver": "^2.0.5", "flat": "^6.0.1", "geodesy": "1.1.3", + "handlebars": "^4.7.8", + "hash-wasm": "^4.12.0", "highlight.js": "^11.9.0", "ieee754": "^1.2.1", "jimp": "^0.22.12", @@ -54,11 +56,12 @@ "js-sha3": "^0.9.3", "jsesc": "^3.0.2", "json5": "^2.2.3", - "jsonpath-plus": "^9.0.0", + "jsonata": "^2.0.3", + "jsonpath-plus": "^10.3.0", "jsonwebtoken": "8.5.1", "jsqr": "^1.4.0", "jsrsasign": "^11.1.0", - "kbpgp": "2.1.15", + "kbpgp": "^2.1.17", "libbzip2-wasm": "0.0.4", "libyara-wasm": "^1.2.1", "lodash": "^4.17.21", @@ -94,6 +97,7 @@ "ua-parser-js": "^1.0.38", "unorm": "^1.6.0", "utf8": "^3.0.0", + "uuid": "^11.1.0", "vkbeautify": "^0.99.3", "xpath": "0.0.34", "xregexp": "^5.1.1", @@ -5133,7 +5137,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, "license": "MIT", "dependencies": { "possible-typed-array-names": "^1.0.0" @@ -8737,6 +8740,22 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es6-object-assign": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", @@ -9840,7 +9859,6 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, "license": "MIT", "dependencies": { "is-callable": "^1.1.3" @@ -9900,14 +9918,16 @@ } }, "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -10842,6 +10862,27 @@ "dev": true, "license": "MIT" }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, "node_modules/has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", @@ -10940,6 +10981,11 @@ "node": ">= 0.10" } }, + "node_modules/hash-wasm": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/hash-wasm/-/hash-wasm-4.12.0.tgz", + "integrity": "sha512-+/2B2rYLb48I/evdOIhP+K/DD2ca2fgBjp6O+GBEnCDk2e4rpeXIK8GvIyRPjTezgmWn9gmKwkQjjx6BtqDHVQ==" + }, "node_modules/hash.js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", @@ -11676,7 +11722,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -12027,6 +12072,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-unc-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", @@ -12464,22 +12524,30 @@ "node": ">=6" } }, + "node_modules/jsonata": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/jsonata/-/jsonata-2.0.6.tgz", + "integrity": "sha512-WhQB5tXQ32qjkx2GYHFw2XbL90u+LLzjofAYwi+86g6SyZeXHz9F1Q0amy3dWRYczshOC3Haok9J4pOCgHtwyQ==", + "engines": { + "node": ">= 8" + } + }, "node_modules/jsonpath-plus": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-9.0.0.tgz", - "integrity": "sha512-bqE77VIDStrOTV/czspZhTn+o27Xx9ZJRGVkdVShEtPoqsIx5yALv3lWVU6y+PqYvWPJNWE7ORCQheQkEe0DDA==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.3.0.tgz", + "integrity": "sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==", "license": "MIT", "dependencies": { - "@jsep-plugin/assignment": "^1.2.1", - "@jsep-plugin/regex": "^1.0.3", - "jsep": "^1.3.8" + "@jsep-plugin/assignment": "^1.3.0", + "@jsep-plugin/regex": "^1.0.4", + "jsep": "^1.4.0" }, "bin": { "jsonpath": "bin/jsonpath-cli.js", "jsonpath-plus": "bin/jsonpath-cli.js" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/jsonwebtoken": { @@ -12563,9 +12631,9 @@ } }, "node_modules/kbpgp": { - "version": "2.1.15", - "resolved": "https://registry.npmjs.org/kbpgp/-/kbpgp-2.1.15.tgz", - "integrity": "sha512-iFdQT+m2Mi2DB14kEFydF2joNe9x3E2VZCGZUt7UXsiZnQx5TtSl4KofP7EPtjHvf7weCxNKlEPSYiiCNMZ2jA==", + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/kbpgp/-/kbpgp-2.1.17.tgz", + "integrity": "sha512-pnjH7amyg6dZLXyF42BKbCTST0l0r1ErunqtFRrJCkHkGJb83cZZmx1pnqNFr+d/ls+5gvcHrZLPfUG5q7oRYw==", "license": "BSD-3-Clause", "dependencies": { "bn": "^1.0.5", @@ -13654,7 +13722,6 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, "license": "MIT" }, "node_modules/netmask": { @@ -13876,6 +13943,15 @@ "node": ">=8" } }, + "node_modules/nightwatch/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/nightwatch/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -13937,9 +14013,9 @@ } }, "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", + "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==", "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { "node": ">= 6.13.0" @@ -14854,19 +14930,20 @@ } }, "node_modules/pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.5.tgz", + "integrity": "sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ==", "license": "MIT", "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "ripemd160": "^2.0.3", + "safe-buffer": "^5.2.1", + "sha.js": "^2.4.12", + "to-buffer": "^1.2.1" }, "engines": { - "node": ">=0.12" + "node": ">= 0.10" } }, "node_modules/peek-readable": { @@ -15124,7 +15201,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -16103,13 +16179,31 @@ } }, "node_modules/ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.3.tgz", + "integrity": "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==", "license": "MIT", "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" + "hash-base": "^3.1.2", + "inherits": "^2.0.4" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ripemd160/node_modules/hash-base": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.2.tgz", + "integrity": "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.1" + }, + "engines": { + "node": ">= 0.8" } }, "node_modules/rison": { @@ -16571,16 +16665,23 @@ "license": "ISC" }, "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", "license": "(MIT AND BSD-3-Clause)", "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" }, "bin": { "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/shebang-command": { @@ -16819,6 +16920,15 @@ "node": ">=0.8.0" } }, + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/socks": { "version": "2.8.3", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", @@ -16859,7 +16969,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -17609,6 +17718,26 @@ "node": ">=14.14" } }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-buffer/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, "node_modules/to-fast-properties": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", @@ -17791,6 +17920,20 @@ "node": ">= 0.6" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ua-parser-js": { "version": "1.0.40", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.40.tgz", @@ -18106,13 +18249,15 @@ } }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "license": "MIT", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/v8flags": { @@ -18799,7 +18944,6 @@ "version": "1.1.18", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==", - "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", @@ -18877,6 +19021,12 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "license": "MIT" + }, "node_modules/worker-loader": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.8.tgz", diff --git a/package.json b/package.json index b3492a8ef2..fe50083c2e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "10.19.4", + "version": "10.20.0", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", @@ -117,7 +117,7 @@ "chi-squared": "^1.1.0", "codepage": "^1.15.0", "crypto-api": "^0.8.5", - "crypto-browserify": "^3.12.0", + "crypto-browserify": "^3.12.1", "crypto-js": "^4.2.0", "ctph.js": "0.0.5", "d3": "7.9.0", @@ -132,6 +132,8 @@ "file-saver": "^2.0.5", "flat": "^6.0.1", "geodesy": "1.1.3", + "handlebars": "^4.7.8", + "hash-wasm": "^4.12.0", "highlight.js": "^11.9.0", "ieee754": "^1.2.1", "jimp": "^0.22.12", @@ -140,11 +142,12 @@ "js-sha3": "^0.9.3", "jsesc": "^3.0.2", "json5": "^2.2.3", - "jsonpath-plus": "^9.0.0", + "jsonata": "^2.0.3", + "jsonpath-plus": "^10.3.0", "jsonwebtoken": "8.5.1", "jsqr": "^1.4.0", "jsrsasign": "^11.1.0", - "kbpgp": "2.1.15", + "kbpgp": "^2.1.17", "libbzip2-wasm": "0.0.4", "libyara-wasm": "^1.2.1", "lodash": "^4.17.21", @@ -180,6 +183,7 @@ "ua-parser-js": "^1.0.38", "unorm": "^1.6.0", "utf8": "^3.0.0", + "uuid": "^11.1.0", "vkbeautify": "^0.99.3", "xpath": "0.0.34", "xregexp": "^5.1.1", diff --git a/src/core/Utils.mjs b/src/core/Utils.mjs index a9c381d766..f9a61b56f7 100755 --- a/src/core/Utils.mjs +++ b/src/core/Utils.mjs @@ -177,7 +177,7 @@ class Utils { */ static printable(str, preserveWs=false, onlyAscii=false) { if (onlyAscii) { - return str.replace(/[^\x20-\x7f]/g, "."); + return str.replace(/[^\x20-\x7e]/g, "."); } // eslint-disable-next-line no-misleading-character-class @@ -327,6 +327,8 @@ class Utils { * * @param {string} str * @param {string} type - One of "Binary", "Hex", "Decimal", "Base64", "UTF8" or "Latin1" + * @param {string} [delim="Auto"] - (Hex only) Delimiter used to split the input string. Set to "Auto" by default. + * @param {boolean} [throwError=false] - (Hex only) Whether to throw an error on invalid input. Defaults to false. * @returns {byteArray} * * @example @@ -339,12 +341,12 @@ class Utils { * // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130] * Utils.convertToByteArray("0JfQtNGA0LDQstGB0YLQstGD0LnRgtC1", "base64"); */ - static convertToByteArray(str, type) { + static convertToByteArray(str, type, delim = "Auto", throwError = false) { switch (type.toLowerCase()) { case "binary": return fromBinary(str); case "hex": - return fromHex(str); + return fromHex(str, delim, 2, throwError); case "decimal": return fromDecimal(str); case "base64": diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 71b311e64c..aac00ca1ca 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -26,6 +26,8 @@ "From Base45", "To Base58", "From Base58", + "To Bech32", + "From Bech32", "To Base62", "From Base62", "To Base64", @@ -294,6 +296,7 @@ "To Upper case", "To Lower case", "Swap case", + "Alternating Caps", "To Case Insensitive Regex", "From Case Insensitive Regex", "Add line numbers", @@ -369,11 +372,13 @@ "Regular expression", "XPath expression", "JPath expression", + "Jsonata Query", "CSS selector", "Extract EXIF", "Extract ID3", "Extract Files", - "RAKE" + "RAKE", + "Template" ] }, { @@ -404,6 +409,7 @@ "name": "Hashing", "ops": [ "Analyse hash", + "Generate all checksums", "Generate all hashes", "MD2", "MD4", @@ -422,6 +428,7 @@ "Snefru", "BLAKE2b", "BLAKE2s", + "BLAKE3", "GOST Hash", "Streebog", "SSDEEP", @@ -446,7 +453,8 @@ "Adler-32 Checksum", "Luhn Checksum", "CRC Checksum", - "TCP/IP Checksum" + "TCP/IP Checksum", + "XOR Checksum" ] }, { @@ -545,6 +553,7 @@ "Pseudo-Random Number Generator", "Generate De Bruijn Sequence", "Generate UUID", + "Analyse UUID", "Generate TOTP", "Generate HOTP", "Generate QR Code", diff --git a/src/core/lib/Bech32.mjs b/src/core/lib/Bech32.mjs new file mode 100644 index 0000000000..6b87a1427e --- /dev/null +++ b/src/core/lib/Bech32.mjs @@ -0,0 +1,371 @@ +/** + * Pure JavaScript implementation of Bech32 and Bech32m encoding. + * + * Bech32 is defined in BIP-0173: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki + * Bech32m is defined in BIP-0350: https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki + * + * @author Medjedtxm + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + */ + +import OperationError from "../errors/OperationError.mjs"; + +/** Bech32 character set (32 characters, excludes 1, b, i, o) */ +const CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; + +/** Reverse lookup table for decoding */ +const CHARSET_REV = {}; +for (let i = 0; i < CHARSET.length; i++) { + CHARSET_REV[CHARSET[i]] = i; +} + +/** Checksum constant for Bech32 (BIP-0173) */ +const BECH32_CONST = 1; + +/** Checksum constant for Bech32m (BIP-0350) */ +const BECH32M_CONST = 0x2bc830a3; + +/** Generator polynomial coefficients for checksum */ +const GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]; + +/** + * Compute the polymod checksum + * @param {number[]} values - Array of 5-bit values + * @returns {number} - Checksum value + */ +function polymod(values) { + let chk = 1; + for (const v of values) { + const top = chk >> 25; + chk = ((chk & 0x1ffffff) << 5) ^ v; + for (let i = 0; i < 5; i++) { + if ((top >> i) & 1) { + chk ^= GENERATOR[i]; + } + } + } + return chk; +} + +/** + * Expand HRP for checksum computation + * @param {string} hrp - Human-readable part (lowercase) + * @returns {number[]} - Expanded values + */ +function hrpExpand(hrp) { + const result = []; + for (let i = 0; i < hrp.length; i++) { + result.push(hrp.charCodeAt(i) >> 5); + } + result.push(0); + for (let i = 0; i < hrp.length; i++) { + result.push(hrp.charCodeAt(i) & 31); + } + return result; +} + +/** + * Verify checksum of a Bech32/Bech32m string + * @param {string} hrp - Human-readable part (lowercase) + * @param {number[]} data - Data including checksum (5-bit values) + * @param {string} encoding - "Bech32" or "Bech32m" + * @returns {boolean} - True if checksum is valid + */ +function verifyChecksum(hrp, data, encoding) { + const constant = encoding === "Bech32m" ? BECH32M_CONST : BECH32_CONST; + return polymod(hrpExpand(hrp).concat(data)) === constant; +} + +/** + * Create checksum for Bech32/Bech32m encoding + * @param {string} hrp - Human-readable part (lowercase) + * @param {number[]} data - Data values (5-bit) + * @param {string} encoding - "Bech32" or "Bech32m" + * @returns {number[]} - 6 checksum values + */ +function createChecksum(hrp, data, encoding) { + const constant = encoding === "Bech32m" ? BECH32M_CONST : BECH32_CONST; + const values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]); + const mod = polymod(values) ^ constant; + const result = []; + for (let i = 0; i < 6; i++) { + result.push((mod >> (5 * (5 - i))) & 31); + } + return result; +} + +/** + * Convert 8-bit bytes to 5-bit words + * @param {number[]|Uint8Array} data - Input bytes + * @returns {number[]} - 5-bit words + */ +export function toWords(data) { + let value = 0; + let bits = 0; + const result = []; + + for (let i = 0; i < data.length; i++) { + value = (value << 8) | data[i]; + bits += 8; + + while (bits >= 5) { + bits -= 5; + result.push((value >> bits) & 31); + } + } + + // Pad remaining bits + if (bits > 0) { + result.push((value << (5 - bits)) & 31); + } + + return result; +} + +/** + * Convert 5-bit words to 8-bit bytes + * @param {number[]} words - 5-bit words + * @returns {number[]} - Output bytes + */ +export function fromWords(words) { + let value = 0; + let bits = 0; + const result = []; + + for (let i = 0; i < words.length; i++) { + value = (value << 5) | words[i]; + bits += 5; + + while (bits >= 8) { + bits -= 8; + result.push((value >> bits) & 255); + } + } + + // Check for invalid padding per BIP-0173 + // Condition 1: Cannot have 5+ bits remaining (would indicate incomplete byte) + if (bits >= 5) { + throw new OperationError("Invalid padding: too many bits remaining"); + } + // Condition 2: Remaining padding bits must all be zero + if (bits > 0) { + const paddingValue = (value << (8 - bits)) & 255; + if (paddingValue !== 0) { + throw new OperationError("Invalid padding: non-zero bits in padding"); + } + } + + return result; +} + +/** + * Encode data to Bech32/Bech32m string + * + * @param {string} hrp - Human-readable part + * @param {number[]|Uint8Array} data - Data bytes to encode + * @param {string} encoding - "Bech32" or "Bech32m" + * @param {boolean} segwit - If true, treat first byte as witness version (for Bitcoin SegWit) + * @returns {string} - Encoded Bech32/Bech32m string + */ +export function encode(hrp, data, encoding = "Bech32", segwit = false) { + // Validate HRP + if (!hrp || hrp.length === 0) { + throw new OperationError("Human-Readable Part (HRP) cannot be empty."); + } + + // Check HRP characters (ASCII 33-126) + for (let i = 0; i < hrp.length; i++) { + const c = hrp.charCodeAt(i); + if (c < 33 || c > 126) { + throw new OperationError(`HRP contains invalid character at position ${i}. Only printable ASCII characters (33-126) are allowed.`); + } + } + + // Convert HRP to lowercase + const hrpLower = hrp.toLowerCase(); + + let words; + if (segwit && data.length >= 2) { + // SegWit encoding: first byte is witness version (0-16), rest is witness program + const witnessVersion = data[0]; + if (witnessVersion > 16) { + throw new OperationError(`Invalid witness version: ${witnessVersion}. Must be 0-16.`); + } + const witnessProgram = Array.prototype.slice.call(data, 1); + + // Validate witness program length per BIP-0141 + if (witnessProgram.length < 2 || witnessProgram.length > 40) { + throw new OperationError(`Invalid witness program length: ${witnessProgram.length}. Must be 2-40 bytes.`); + } + if (witnessVersion === 0 && witnessProgram.length !== 20 && witnessProgram.length !== 32) { + throw new OperationError(`Invalid witness program length for v0: ${witnessProgram.length}. Must be 20 or 32 bytes.`); + } + + // Witness version is kept as single 5-bit value, program is converted + words = [witnessVersion].concat(toWords(witnessProgram)); + } else { + // Generic encoding: convert all bytes to 5-bit words + words = toWords(data); + } + + // Create checksum + const checksum = createChecksum(hrpLower, words, encoding); + + // Build result string + let result = hrpLower + "1"; + for (const w of words.concat(checksum)) { + result += CHARSET[w]; + } + + // Check maximum length (90 characters) + if (result.length > 90) { + throw new OperationError(`Encoded string exceeds maximum length of 90 characters (got ${result.length}). Consider using smaller input data.`); + } + + return result; +} + +/** + * Decode a Bech32/Bech32m string + * + * @param {string} str - Bech32/Bech32m encoded string + * @param {string} encoding - "Bech32", "Bech32m", or "Auto-detect" + * @returns {{hrp: string, data: number[]}} - Decoded HRP and data bytes + */ +export function decode(str, encoding = "Auto-detect") { + // Check for empty input + if (!str || str.length === 0) { + throw new OperationError("Input cannot be empty."); + } + + // Check maximum length + if (str.length > 90) { + throw new OperationError(`Invalid Bech32 string: exceeds maximum length of 90 characters (got ${str.length}).`); + } + + // Check for mixed case + const hasUpper = /[A-Z]/.test(str); + const hasLower = /[a-z]/.test(str); + if (hasUpper && hasLower) { + throw new OperationError("Invalid Bech32 string: mixed case is not allowed. Use all uppercase or all lowercase."); + } + + // Convert to lowercase for processing + str = str.toLowerCase(); + + // Find separator (last occurrence of '1') + const sepIndex = str.lastIndexOf("1"); + if (sepIndex === -1) { + throw new OperationError("Invalid Bech32 string: no separator '1' found."); + } + + if (sepIndex === 0) { + throw new OperationError("Invalid Bech32 string: Human-Readable Part (HRP) cannot be empty."); + } + + if (sepIndex + 7 > str.length) { + throw new OperationError("Invalid Bech32 string: data part is too short (minimum 6 characters for checksum)."); + } + + // Extract HRP and data part + const hrp = str.substring(0, sepIndex); + const dataPart = str.substring(sepIndex + 1); + + // Validate HRP characters + for (let i = 0; i < hrp.length; i++) { + const c = hrp.charCodeAt(i); + if (c < 33 || c > 126) { + throw new OperationError(`HRP contains invalid character at position ${i}.`); + } + } + + // Decode data characters to 5-bit values + const data = []; + for (let i = 0; i < dataPart.length; i++) { + const c = dataPart[i]; + if (CHARSET_REV[c] === undefined) { + throw new OperationError(`Invalid character '${c}' at position ${sepIndex + 1 + i}.`); + } + data.push(CHARSET_REV[c]); + } + + // Verify checksum + let usedEncoding; + if (encoding === "Bech32") { + if (!verifyChecksum(hrp, data, "Bech32")) { + throw new OperationError("Invalid Bech32 checksum."); + } + usedEncoding = "Bech32"; + } else if (encoding === "Bech32m") { + if (!verifyChecksum(hrp, data, "Bech32m")) { + throw new OperationError("Invalid Bech32m checksum."); + } + usedEncoding = "Bech32m"; + } else { + // Auto-detect: try Bech32 first, then Bech32m + if (verifyChecksum(hrp, data, "Bech32")) { + usedEncoding = "Bech32"; + } else if (verifyChecksum(hrp, data, "Bech32m")) { + usedEncoding = "Bech32m"; + } else { + throw new OperationError("Invalid Bech32/Bech32m string: checksum verification failed."); + } + } + + // Remove checksum (last 6 values) + const words = data.slice(0, data.length - 6); + + // Check if this is likely a SegWit address (Bitcoin, Litecoin, etc.) + // For SegWit, the first 5-bit word is the witness version (0-16) + // and should be extracted separately, not bit-converted with the rest + const segwitHrps = ["bc", "tb", "ltc", "tltc", "bcrt"]; + const couldBeSegWit = segwitHrps.includes(hrp) && words.length > 0 && words[0] <= 16; + + let bytes; + let witnessVersion = null; + + if (couldBeSegWit) { + // Try SegWit decode first + try { + witnessVersion = words[0]; + const programWords = words.slice(1); + const programBytes = fromWords(programWords); + + // Validate SegWit witness program length (20 or 32 bytes for v0, 2-40 for others) + const validV0 = witnessVersion === 0 && (programBytes.length === 20 || programBytes.length === 32); + const validOther = witnessVersion !== 0 && programBytes.length >= 2 && programBytes.length <= 40; + + if (validV0 || validOther) { + // Valid SegWit address + bytes = [witnessVersion, ...programBytes]; + } else { + // Not valid SegWit, fall back to generic decode + witnessVersion = null; + bytes = fromWords(words); + } + } catch (e) { + // SegWit decode failed, try generic decode + witnessVersion = null; + try { + bytes = fromWords(words); + } catch (e2) { + throw new OperationError(`Failed to decode data: ${e2.message}`); + } + } + } else { + // Generic Bech32: convert all words + try { + bytes = fromWords(words); + } catch (e) { + throw new OperationError(`Failed to decode data: ${e.message}`); + } + } + + return { + hrp: hrp, + data: bytes, + encoding: usedEncoding, + witnessVersion: witnessVersion + }; +} diff --git a/src/core/lib/Hex.mjs b/src/core/lib/Hex.mjs index 78e1ad58ca..71ce7a131a 100644 --- a/src/core/lib/Hex.mjs +++ b/src/core/lib/Hex.mjs @@ -91,6 +91,7 @@ export function toHexFast(data) { * @param {string} data * @param {string} [delim] * @param {number} [byteLen=2] + * @param {boolean} [throwError=false] * @returns {byteArray} * * @example @@ -100,7 +101,7 @@ export function toHexFast(data) { * // returns [10,20,30] * fromHex("0a:14:1e", "Colon"); */ -export function fromHex(data, delim="Auto", byteLen=2) { +export function fromHex(data, delim="Auto", byteLen=2, throwError=false) { if (byteLen < 1 || Math.round(byteLen) !== byteLen) throw new OperationError("Byte length must be a positive integer"); @@ -114,7 +115,11 @@ export function fromHex(data, delim="Auto", byteLen=2) { const output = []; for (let i = 0; i < data.length; i++) { for (let j = 0; j < data[i].length; j += byteLen) { - output.push(parseInt(data[i].substr(j, byteLen), 16)); + const chunk = data[i].substr(j, byteLen); + if (throwError && /[^a-f\d]/i.test(chunk)) { + throw new OperationError(`Invalid hex character in chunk "${chunk}"`); + } + output.push(parseInt(chunk, 16)); } } return output; diff --git a/src/core/lib/JA4.mjs b/src/core/lib/JA4.mjs index f600f4d893..58422bcad4 100644 --- a/src/core/lib/JA4.mjs +++ b/src/core/lib/JA4.mjs @@ -91,9 +91,7 @@ export function toJA4(bytes) { let alpn = "00"; for (const ext of tlsr.handshake.value.extensions.value) { if (ext.type.value === "application_layer_protocol_negotiation") { - alpn = parseFirstALPNValue(ext.value.data); - alpn = alpn.charAt(0) + alpn.charAt(alpn.length - 1); - if (alpn.charCodeAt(0) > 127) alpn = "99"; + alpn = alpnFingerprint(parseFirstALPNValue(ext.value.data)); break; } } @@ -212,9 +210,7 @@ export function toJA4S(bytes) { let alpn = "00"; for (const ext of tlsr.handshake.value.extensions.value) { if (ext.type.value === "application_layer_protocol_negotiation") { - alpn = parseFirstALPNValue(ext.value.data); - alpn = alpn.charAt(0) + alpn.charAt(alpn.length - 1); - if (alpn.charCodeAt(0) > 127) alpn = "99"; + alpn = alpnFingerprint(parseFirstALPNValue(ext.value.data)); break; } } @@ -262,3 +258,33 @@ function tlsVersionMapper(version) { default: return "00"; // Unknown } } + +/** + * Checks if a byte is ASCII alphanumeric (0-9, A-Z, a-z). + * @param {number} byte + * @returns {boolean} + */ +function isAlphanumeric(byte) { + return (byte >= 0x30 && byte <= 0x39) || + (byte >= 0x41 && byte <= 0x5A) || + (byte >= 0x61 && byte <= 0x7A); +} + +/** + * Computes the 2-character ALPN fingerprint from raw ALPN bytes. + * If both first and last bytes are ASCII alphanumeric, returns their characters. + * Otherwise, returns first hex digit of first byte + last hex digit of last byte. + * @param {Uint8Array|null} rawBytes + * @returns {string} + */ +function alpnFingerprint(rawBytes) { + if (!rawBytes || rawBytes.length === 0) return "00"; + const firstByte = rawBytes[0]; + const lastByte = rawBytes[rawBytes.length - 1]; + if (isAlphanumeric(firstByte) && isAlphanumeric(lastByte)) { + return String.fromCharCode(firstByte) + String.fromCharCode(lastByte); + } + const firstHex = firstByte.toString(16).padStart(2, "0"); + const lastHex = lastByte.toString(16).padStart(2, "0"); + return firstHex[0] + lastHex[1]; +} diff --git a/src/core/lib/TLS.mjs b/src/core/lib/TLS.mjs index 6373bfa25f..eaf661a89b 100644 --- a/src/core/lib/TLS.mjs +++ b/src/core/lib/TLS.mjs @@ -863,15 +863,15 @@ export function parseHighestSupportedVersion(bytes) { } /** - * Parses the application_layer_protocol_negotiation extension and returns the first value. + * Parses the application_layer_protocol_negotiation extension and returns the first value as raw bytes. * @param {Uint8Array} bytes - * @returns {number} + * @returns {Uint8Array|null} */ export function parseFirstALPNValue(bytes) { const s = new Stream(bytes); const alpnExtLen = s.readInt(2); - if (alpnExtLen < 3) return "00"; + if (alpnExtLen < 2) return null; const strLen = s.readInt(1); - if (strLen < 2) return "00"; - return s.readString(strLen); + if (strLen < 1) return null; + return s.getBytes(strLen); } diff --git a/src/core/operations/AlternatingCaps.mjs b/src/core/operations/AlternatingCaps.mjs new file mode 100644 index 0000000000..2d54867c4a --- /dev/null +++ b/src/core/operations/AlternatingCaps.mjs @@ -0,0 +1,53 @@ +/** + * @author sw5678 + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * Alternating caps operation + */ +class AlternatingCaps extends Operation { + + /** + * AlternatingCaps constructor + */ + constructor() { + super(); + + this.name = "Alternating Caps"; + this.module = "Default"; + this.description = "Alternating caps, also known as studly caps, sticky caps, or spongecase is a form of text notation in which the capitalization of letters varies by some pattern, or arbitrarily. An example of this would be spelling 'alternative caps' as 'aLtErNaTiNg CaPs'."; + this.infoURL = "https://en.wikipedia.org/wiki/Alternating_caps"; + this.inputType = "string"; + this.outputType = "string"; + this.args= []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let output = ""; + let previousCaps = true; + for (let i = 0; i < input.length; i++) { + // Check if the element is a letter + if (!RegExp(/^\p{L}/, "u").test(input[i])) { + output += input[i]; + } else if (previousCaps) { + output += input[i].toLowerCase(); + previousCaps = false; + } else { + output += input[i].toUpperCase(); + previousCaps = true; + } + } + return output; + } +} + +export default AlternatingCaps; diff --git a/src/core/operations/AnalyseUUID.mjs b/src/core/operations/AnalyseUUID.mjs new file mode 100644 index 0000000000..b350601793 --- /dev/null +++ b/src/core/operations/AnalyseUUID.mjs @@ -0,0 +1,48 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import * as uuid from "uuid"; + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Analyse UUID operation + */ +class AnalyseUUID extends Operation { + + /** + * AnalyseUUID constructor + */ + constructor() { + super(); + + this.name = "Analyse UUID"; + this.module = "Crypto"; + this.description = "Tries to determine information about a given UUID and suggests which version may have been used to generate it"; + this.infoURL = "https://wikipedia.org/wiki/Universally_unique_identifier"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + try { + const uuidVersion = uuid.version(input); + return "UUID version: " + uuidVersion; + } catch (error) { + throw new OperationError("Invalid UUID"); + } + } + +} + +export default AnalyseUUID; diff --git a/src/core/operations/BLAKE3.mjs b/src/core/operations/BLAKE3.mjs new file mode 100644 index 0000000000..0f686120aa --- /dev/null +++ b/src/core/operations/BLAKE3.mjs @@ -0,0 +1,58 @@ +/** + * @author xumptex [xumptex@outlook.fr] + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { blake3 } from "hash-wasm"; +/** + * BLAKE3 operation + */ +class BLAKE3 extends Operation { + + /** + * BLAKE3 constructor + */ + constructor() { + super(); + + this.name = "BLAKE3"; + this.module = "Hashing"; + this.description = "Hashes the input using BLAKE3 (UTF-8 encoded), with an optional key (also UTF-8), and outputs the result in hexadecimal format."; + this.infoURL = "https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE3"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Size (bytes)", + "type": "number" + }, { + "name": "Key", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = args[1]; + const size = args[0]; + // Check if the user want a key hash or not + if (key === "") { + return blake3(input, size*8); + } if (key.length !== 32) { + throw new OperationError("The key must be exactly 32 bytes long"); + } + return blake3(input, size*8, key); + } + +} + +export default BLAKE3; diff --git a/src/core/operations/ECDSAVerify.mjs b/src/core/operations/ECDSAVerify.mjs index 7e46e867de..1f8a53ea64 100644 --- a/src/core/operations/ECDSAVerify.mjs +++ b/src/core/operations/ECDSAVerify.mjs @@ -9,6 +9,7 @@ import OperationError from "../errors/OperationError.mjs"; import { fromBase64 } from "../lib/Base64.mjs"; import { toHexFast } from "../lib/Hex.mjs"; import r from "jsrsasign"; +import Utils from "../Utils.mjs"; /** * ECDSA Verify operation @@ -59,6 +60,11 @@ class ECDSAVerify extends Operation { name: "Message", type: "text", value: "" + }, + { + name: "Message format", + type: "option", + value: ["Raw", "Hex", "Base64"] } ]; } @@ -70,7 +76,7 @@ class ECDSAVerify extends Operation { */ run(input, args) { let inputFormat = args[0]; - const [, mdAlgo, keyPem, msg] = args; + const [, mdAlgo, keyPem, msg, msgFormat] = args; if (keyPem.replace("-----BEGIN PUBLIC KEY-----", "").length === 0) { throw new OperationError("Please enter a public key."); @@ -145,7 +151,8 @@ class ECDSAVerify extends Operation { throw new OperationError("Provided key is not a public key."); } sig.init(key); - sig.updateString(msg); + const messageStr = Utils.convertToByteString(msg, msgFormat); + sig.updateString(messageStr); const result = sig.verify(signatureASN1Hex); return result ? "Verified OK" : "Verification Failure"; } diff --git a/src/core/operations/ExtractEmailAddresses.mjs b/src/core/operations/ExtractEmailAddresses.mjs index f50e1aaf50..34b838ab3b 100644 --- a/src/core/operations/ExtractEmailAddresses.mjs +++ b/src/core/operations/ExtractEmailAddresses.mjs @@ -51,7 +51,7 @@ class ExtractEmailAddresses extends Operation { run(input, args) { const [displayTotal, sort, unique] = args, // email regex from: https://www.regextester.com/98066 - regex = /(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?\.)+[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}\])/ig; + regex = /(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?\.)+[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\])/ig; const results = search( input, diff --git a/src/core/operations/ExtractIPAddresses.mjs b/src/core/operations/ExtractIPAddresses.mjs index 97b52478d3..b74ec8fe2c 100644 --- a/src/core/operations/ExtractIPAddresses.mjs +++ b/src/core/operations/ExtractIPAddresses.mjs @@ -21,7 +21,7 @@ class ExtractIPAddresses extends Operation { this.name = "Extract IP addresses"; this.module = "Regex"; - this.description = "Extracts all IPv4 and IPv6 addresses.

Warning: Given a string 710.65.0.456, this will match 10.65.0.45 so always check the original input!"; + this.description = "Extracts all IPv4 and IPv6 addresses.

Warning: Given a string 1.2.3.4.5.6.7.8, this will match 1.2.3.4 and 5.6.7.8 so always check the original input!"; this.inputType = "string"; this.outputType = "string"; this.args = [ @@ -65,7 +65,21 @@ class ExtractIPAddresses extends Operation { */ run(input, args) { const [includeIpv4, includeIpv6, removeLocal, displayTotal, sort, unique] = args, - ipv4 = "(?:(?:\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d|\\d)(?:\\/\\d{1,2})?", + + // IPv4 decimal groups can have values 0 to 255. To construct a regex the following sub-regex is reused: + ipv4DecimalByte = "(?:25[0-5]|2[0-4]\\d|1?[0-9]\\d|\\d)", + ipv4OctalByte = "(?:0[1-3]?[0-7]{1,2})", + + // Look behind and ahead will be used to exclude matches with additional decimal digits left and right of IP address + lookBehind = "(?
Bech32m (BIP-0350) is an updated version used for Bitcoin Taproot addresses.

Auto-detect will attempt Bech32 first, then Bech32m if the checksum fails.

Output format options allow you to see the Human-Readable Part (HRP) along with the decoded data."; + this.infoURL = "https://wikipedia.org/wiki/Bech32"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Encoding", + "type": "option", + "value": ["Auto-detect", "Bech32", "Bech32m"] + }, + { + "name": "Output Format", + "type": "option", + "value": ["Raw", "Hex", "Bitcoin scriptPubKey", "HRP: Hex", "JSON"] + } + ]; + this.checks = [ + { + // Bitcoin mainnet SegWit/Taproot addresses + pattern: "^bc1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,87}$", + flags: "i", + args: ["Auto-detect", "Hex"] + }, + { + // Bitcoin testnet addresses + pattern: "^tb1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,87}$", + flags: "i", + args: ["Auto-detect", "Hex"] + }, + { + // AGE public keys + pattern: "^age1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,87}$", + flags: "i", + args: ["Auto-detect", "HRP: Hex"] + }, + { + // AGE secret keys + pattern: "^AGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JN54KHCE6MUA7L]{6,87}$", + flags: "", + args: ["Auto-detect", "HRP: Hex"] + }, + { + // Litecoin mainnet addresses + pattern: "^ltc1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,87}$", + flags: "i", + args: ["Auto-detect", "Hex"] + }, + { + // Generic bech32 pattern + pattern: "^[a-z]{1,83}1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,}$", + flags: "i", + args: ["Auto-detect", "Hex"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const encoding = args[0]; + const outputFormat = args[1]; + + input = input.trim(); + + if (input.length === 0) { + return ""; + } + + const decoded = decode(input, encoding); + + // Format output based on selected option + switch (outputFormat) { + case "Raw": + return decoded.data.map(b => String.fromCharCode(b)).join(""); + + case "Hex": + return toHex(decoded.data, ""); + + case "Bitcoin scriptPubKey": { + // Convert to Bitcoin scriptPubKey format as shown in BIP-0173/BIP-0350 + // Format: [OP_version][length][witness_program] + // OP_0 = 0x00, OP_1-OP_16 = 0x51-0x60 + if (decoded.witnessVersion === null || decoded.data.length < 2) { + // Not a SegWit address, fall back to hex + return toHex(decoded.data, ""); + } + const witnessVersion = decoded.data[0]; + const witnessProgram = decoded.data.slice(1); + + // Convert witness version to OP code + let opCode; + if (witnessVersion === 0) { + opCode = 0x00; // OP_0 + } else if (witnessVersion >= 1 && witnessVersion <= 16) { + opCode = 0x50 + witnessVersion; // OP_1 = 0x51, ..., OP_16 = 0x60 + } else { + // Invalid witness version, fall back to hex + return toHex(decoded.data, ""); + } + + // Build scriptPubKey: [OP_version][length][program] + const scriptPubKey = [opCode, witnessProgram.length, ...witnessProgram]; + return toHex(scriptPubKey, ""); + } + + case "HRP: Hex": + return `${decoded.hrp}: ${toHex(decoded.data, "")}`; + + case "JSON": + return JSON.stringify({ + hrp: decoded.hrp, + encoding: decoded.encoding, + data: toHex(decoded.data, "") + }, null, 2); + + default: + return toHex(decoded.data, ""); + } + } + +} + +export default FromBech32; diff --git a/src/core/operations/FromHexdump.mjs b/src/core/operations/FromHexdump.mjs index e8c25441f4..6fd3c1dc79 100644 --- a/src/core/operations/FromHexdump.mjs +++ b/src/core/operations/FromHexdump.mjs @@ -43,7 +43,7 @@ class FromHexdump extends Operation { */ run(input, args) { const output = [], - regex = /^\s*(?:[\dA-F]{4,16}h?:?)?[ \t]+((?:[\dA-F]{2} ){1,8}(?:[ \t]|[\dA-F]{2}-)(?:[\dA-F]{2} ){1,8}|(?:[\dA-F]{4} )*[\dA-F]{4}|(?:[\dA-F]{2} )*[\dA-F]{2})/igm; + regex = /^\s*(?:[\dA-F]{4,16}h?:?)?[ \t]+((?:[\dA-F]{2} ){1,8}(?:[ \t]|[\dA-F]{2}-)(?:[\dA-F]{2} ){1,8}|(?:[\dA-F]{4} )+(?:[\dA-F]{2})?|(?:[\dA-F]{2} )*[\dA-F]{2})/igm; let block, line; while ((block = regex.exec(input))) { diff --git a/src/core/operations/GenerateAllChecksums.mjs b/src/core/operations/GenerateAllChecksums.mjs new file mode 100644 index 0000000000..b5a3d1525d --- /dev/null +++ b/src/core/operations/GenerateAllChecksums.mjs @@ -0,0 +1,254 @@ +/** + * @author r4mos [2k95ljkhg@mozmail.com] + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Adler32Checksum from "./Adler32Checksum.mjs"; +import CRCChecksum from "./CRCChecksum.mjs"; +import Fletcher8Checksum from "./Fletcher8Checksum.mjs"; +import Fletcher16Checksum from "./Fletcher16Checksum.mjs"; +import Fletcher32Checksum from "./Fletcher32Checksum.mjs"; +import Fletcher64Checksum from "./Fletcher64Checksum.mjs"; + +/** + * Generate all checksums operation + */ +class GenerateAllChecksums extends Operation { + + /** + * GenerateAllChecksums constructor + */ + constructor() { + super(); + + this.name = "Generate all checksums"; + this.module = "Crypto"; + this.description = "Generates all available checksums for the input."; + this.infoURL = "https://wikipedia.org/wiki/Checksum"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "Length (bits)", + type: "option", + value: [ + "All", "3", "4", "5", "6", "7", "8", "10", "11", "12", "13", "14", "15", "16", "17", "21", "24", "30", "31", "32", "40", "64", "82" + ] + }, + { + name: "Include names", + type: "boolean", + value: true + }, + ]; + + const adler32 = new Adler32Checksum; + const crc = new CRCChecksum; + const fletcher8 = new Fletcher8Checksum; + const fletcher16 = new Fletcher16Checksum; + const fletcher32 = new Fletcher32Checksum; + const fletcher64 = new Fletcher64Checksum; + this.checksums = [ + {name: "CRC-3/GSM", algo: crc, params: ["CRC-3/GSM"]}, + {name: "CRC-3/ROHC", algo: crc, params: ["CRC-3/ROHC"]}, + {name: "CRC-4/G-704", algo: crc, params: ["CRC-4/G-704"]}, + {name: "CRC-4/INTERLAKEN", algo: crc, params: ["CRC-4/INTERLAKEN"]}, + {name: "CRC-4/ITU", algo: crc, params: ["CRC-4/ITU"]}, + {name: "CRC-5/EPC", algo: crc, params: ["CRC-5/EPC"]}, + {name: "CRC-5/EPC-C1G2", algo: crc, params: ["CRC-5/EPC-C1G2"]}, + {name: "CRC-5/G-704", algo: crc, params: ["CRC-5/G-704"]}, + {name: "CRC-5/ITU", algo: crc, params: ["CRC-5/ITU"]}, + {name: "CRC-5/USB", algo: crc, params: ["CRC-5/USB"]}, + {name: "CRC-6/CDMA2000-A", algo: crc, params: ["CRC-6/CDMA2000-A"]}, + {name: "CRC-6/CDMA2000-B", algo: crc, params: ["CRC-6/CDMA2000-B"]}, + {name: "CRC-6/DARC", algo: crc, params: ["CRC-6/DARC"]}, + {name: "CRC-6/G-704", algo: crc, params: ["CRC-6/G-704"]}, + {name: "CRC-6/GSM", algo: crc, params: ["CRC-6/GSM"]}, + {name: "CRC-6/ITU", algo: crc, params: ["CRC-6/ITU"]}, + {name: "CRC-7/MMC", algo: crc, params: ["CRC-7/MMC"]}, + {name: "CRC-7/ROHC", algo: crc, params: ["CRC-7/ROHC"]}, + {name: "CRC-7/UMTS", algo: crc, params: ["CRC-7/UMTS"]}, + {name: "CRC-8", algo: crc, params: ["CRC-8"]}, + {name: "CRC-8/8H2F", algo: crc, params: ["CRC-8/8H2F"]}, + {name: "CRC-8/AES", algo: crc, params: ["CRC-8/AES"]}, + {name: "CRC-8/AUTOSAR", algo: crc, params: ["CRC-8/AUTOSAR"]}, + {name: "CRC-8/BLUETOOTH", algo: crc, params: ["CRC-8/BLUETOOTH"]}, + {name: "CRC-8/CDMA2000", algo: crc, params: ["CRC-8/CDMA2000"]}, + {name: "CRC-8/DARC", algo: crc, params: ["CRC-8/DARC"]}, + {name: "CRC-8/DVB-S2", algo: crc, params: ["CRC-8/DVB-S2"]}, + {name: "CRC-8/EBU", algo: crc, params: ["CRC-8/EBU"]}, + {name: "CRC-8/GSM-A", algo: crc, params: ["CRC-8/GSM-A"]}, + {name: "CRC-8/GSM-B", algo: crc, params: ["CRC-8/GSM-B"]}, + {name: "CRC-8/HITAG", algo: crc, params: ["CRC-8/HITAG"]}, + {name: "CRC-8/I-432-1", algo: crc, params: ["CRC-8/I-432-1"]}, + {name: "CRC-8/I-CODE", algo: crc, params: ["CRC-8/I-CODE"]}, + {name: "CRC-8/ITU", algo: crc, params: ["CRC-8/ITU"]}, + {name: "CRC-8/LTE", algo: crc, params: ["CRC-8/LTE"]}, + {name: "CRC-8/MAXIM", algo: crc, params: ["CRC-8/MAXIM"]}, + {name: "CRC-8/MAXIM-DOW", algo: crc, params: ["CRC-8/MAXIM-DOW"]}, + {name: "CRC-8/MIFARE-MAD", algo: crc, params: ["CRC-8/MIFARE-MAD"]}, + {name: "CRC-8/NRSC-5", algo: crc, params: ["CRC-8/NRSC-5"]}, + {name: "CRC-8/OPENSAFETY", algo: crc, params: ["CRC-8/OPENSAFETY"]}, + {name: "CRC-8/ROHC", algo: crc, params: ["CRC-8/ROHC"]}, + {name: "CRC-8/SAE-J1850", algo: crc, params: ["CRC-8/SAE-J1850"]}, + {name: "CRC-8/SAE-J1850-ZERO", algo: crc, params: ["CRC-8/SAE-J1850-ZERO"]}, + {name: "CRC-8/SMBUS", algo: crc, params: ["CRC-8/SMBUS"]}, + {name: "CRC-8/TECH-3250", algo: crc, params: ["CRC-8/TECH-3250"]}, + {name: "CRC-8/WCDMA", algo: crc, params: ["CRC-8/WCDMA"]}, + {name: "Fletcher-8", algo: fletcher8, params: []}, + {name: "CRC-10/ATM", algo: crc, params: ["CRC-10/ATM"]}, + {name: "CRC-10/CDMA2000", algo: crc, params: ["CRC-10/CDMA2000"]}, + {name: "CRC-10/GSM", algo: crc, params: ["CRC-10/GSM"]}, + {name: "CRC-10/I-610", algo: crc, params: ["CRC-10/I-610"]}, + {name: "CRC-11/FLEXRAY", algo: crc, params: ["CRC-11/FLEXRAY"]}, + {name: "CRC-11/UMTS", algo: crc, params: ["CRC-11/UMTS"]}, + {name: "CRC-12/3GPP", algo: crc, params: ["CRC-12/3GPP"]}, + {name: "CRC-12/CDMA2000", algo: crc, params: ["CRC-12/CDMA2000"]}, + {name: "CRC-12/DECT", algo: crc, params: ["CRC-12/DECT"]}, + {name: "CRC-12/GSM", algo: crc, params: ["CRC-12/GSM"]}, + {name: "CRC-12/UMTS", algo: crc, params: ["CRC-12/UMTS"]}, + {name: "CRC-13/BBC", algo: crc, params: ["CRC-13/BBC"]}, + {name: "CRC-14/DARC", algo: crc, params: ["CRC-14/DARC"]}, + {name: "CRC-14/GSM", algo: crc, params: ["CRC-14/GSM"]}, + {name: "CRC-15/CAN", algo: crc, params: ["CRC-15/CAN"]}, + {name: "CRC-15/MPT1327", algo: crc, params: ["CRC-15/MPT1327"]}, + {name: "CRC-16", algo: crc, params: ["CRC-16"]}, + {name: "CRC-16/A", algo: crc, params: ["CRC-16/A"]}, + {name: "CRC-16/ACORN", algo: crc, params: ["CRC-16/ACORN"]}, + {name: "CRC-16/ARC", algo: crc, params: ["CRC-16/ARC"]}, + {name: "CRC-16/AUG-CCITT", algo: crc, params: ["CRC-16/AUG-CCITT"]}, + {name: "CRC-16/AUTOSAR", algo: crc, params: ["CRC-16/AUTOSAR"]}, + {name: "CRC-16/B", algo: crc, params: ["CRC-16/B"]}, + {name: "CRC-16/BLUETOOTH", algo: crc, params: ["CRC-16/BLUETOOTH"]}, + {name: "CRC-16/BUYPASS", algo: crc, params: ["CRC-16/BUYPASS"]}, + {name: "CRC-16/CCITT", algo: crc, params: ["CRC-16/CCITT"]}, + {name: "CRC-16/CCITT-FALSE", algo: crc, params: ["CRC-16/CCITT-FALSE"]}, + {name: "CRC-16/CCITT-TRUE", algo: crc, params: ["CRC-16/CCITT-TRUE"]}, + {name: "CRC-16/CCITT-ZERO", algo: crc, params: ["CRC-16/CCITT-ZERO"]}, + {name: "CRC-16/CDMA2000", algo: crc, params: ["CRC-16/CDMA2000"]}, + {name: "CRC-16/CMS", algo: crc, params: ["CRC-16/CMS"]}, + {name: "CRC-16/DARC", algo: crc, params: ["CRC-16/DARC"]}, + {name: "CRC-16/DDS-110", algo: crc, params: ["CRC-16/DDS-110"]}, + {name: "CRC-16/DECT-R", algo: crc, params: ["CRC-16/DECT-R"]}, + {name: "CRC-16/DECT-X", algo: crc, params: ["CRC-16/DECT-X"]}, + {name: "CRC-16/DNP", algo: crc, params: ["CRC-16/DNP"]}, + {name: "CRC-16/EN-13757", algo: crc, params: ["CRC-16/EN-13757"]}, + {name: "CRC-16/EPC", algo: crc, params: ["CRC-16/EPC"]}, + {name: "CRC-16/EPC-C1G2", algo: crc, params: ["CRC-16/EPC-C1G2"]}, + {name: "CRC-16/GENIBUS", algo: crc, params: ["CRC-16/GENIBUS"]}, + {name: "CRC-16/GSM", algo: crc, params: ["CRC-16/GSM"]}, + {name: "CRC-16/I-CODE", algo: crc, params: ["CRC-16/I-CODE"]}, + {name: "CRC-16/IBM", algo: crc, params: ["CRC-16/IBM"]}, + {name: "CRC-16/IBM-3740", algo: crc, params: ["CRC-16/IBM-3740"]}, + {name: "CRC-16/IBM-SDLC", algo: crc, params: ["CRC-16/IBM-SDLC"]}, + {name: "CRC-16/IEC-61158-2", algo: crc, params: ["CRC-16/IEC-61158-2"]}, + {name: "CRC-16/ISO-HDLC", algo: crc, params: ["CRC-16/ISO-HDLC"]}, + {name: "CRC-16/ISO-IEC-14443-3-A", algo: crc, params: ["CRC-16/ISO-IEC-14443-3-A"]}, + {name: "CRC-16/ISO-IEC-14443-3-B", algo: crc, params: ["CRC-16/ISO-IEC-14443-3-B"]}, + {name: "CRC-16/KERMIT", algo: crc, params: ["CRC-16/KERMIT"]}, + {name: "CRC-16/LHA", algo: crc, params: ["CRC-16/LHA"]}, + {name: "CRC-16/LJ1200", algo: crc, params: ["CRC-16/LJ1200"]}, + {name: "CRC-16/LTE", algo: crc, params: ["CRC-16/LTE"]}, + {name: "CRC-16/M17", algo: crc, params: ["CRC-16/M17"]}, + {name: "CRC-16/MAXIM", algo: crc, params: ["CRC-16/MAXIM"]}, + {name: "CRC-16/MAXIM-DOW", algo: crc, params: ["CRC-16/MAXIM-DOW"]}, + {name: "CRC-16/MCRF4XX", algo: crc, params: ["CRC-16/MCRF4XX"]}, + {name: "CRC-16/MODBUS", algo: crc, params: ["CRC-16/MODBUS"]}, + {name: "CRC-16/NRSC-5", algo: crc, params: ["CRC-16/NRSC-5"]}, + {name: "CRC-16/OPENSAFETY-A", algo: crc, params: ["CRC-16/OPENSAFETY-A"]}, + {name: "CRC-16/OPENSAFETY-B", algo: crc, params: ["CRC-16/OPENSAFETY-B"]}, + {name: "CRC-16/PROFIBUS", algo: crc, params: ["CRC-16/PROFIBUS"]}, + {name: "CRC-16/RIELLO", algo: crc, params: ["CRC-16/RIELLO"]}, + {name: "CRC-16/SPI-FUJITSU", algo: crc, params: ["CRC-16/SPI-FUJITSU"]}, + {name: "CRC-16/T10-DIF", algo: crc, params: ["CRC-16/T10-DIF"]}, + {name: "CRC-16/TELEDISK", algo: crc, params: ["CRC-16/TELEDISK"]}, + {name: "CRC-16/TMS37157", algo: crc, params: ["CRC-16/TMS37157"]}, + {name: "CRC-16/UMTS", algo: crc, params: ["CRC-16/UMTS"]}, + {name: "CRC-16/USB", algo: crc, params: ["CRC-16/USB"]}, + {name: "CRC-16/V-41-LSB", algo: crc, params: ["CRC-16/V-41-LSB"]}, + {name: "CRC-16/V-41-MSB", algo: crc, params: ["CRC-16/V-41-MSB"]}, + {name: "CRC-16/VERIFONE", algo: crc, params: ["CRC-16/VERIFONE"]}, + {name: "CRC-16/X-25", algo: crc, params: ["CRC-16/X-25"]}, + {name: "CRC-16/XMODEM", algo: crc, params: ["CRC-16/XMODEM"]}, + {name: "CRC-16/ZMODEM", algo: crc, params: ["CRC-16/ZMODEM"]}, + {name: "Fletcher-16", algo: fletcher16, params: []}, + {name: "CRC-17/CAN-FD", algo: crc, params: ["CRC-17/CAN-FD"]}, + {name: "CRC-21/CAN-FD", algo: crc, params: ["CRC-21/CAN-FD"]}, + {name: "CRC-24/BLE", algo: crc, params: ["CRC-24/BLE"]}, + {name: "CRC-24/FLEXRAY-A", algo: crc, params: ["CRC-24/FLEXRAY-A"]}, + {name: "CRC-24/FLEXRAY-B", algo: crc, params: ["CRC-24/FLEXRAY-B"]}, + {name: "CRC-24/INTERLAKEN", algo: crc, params: ["CRC-24/INTERLAKEN"]}, + {name: "CRC-24/LTE-A", algo: crc, params: ["CRC-24/LTE-A"]}, + {name: "CRC-24/LTE-B", algo: crc, params: ["CRC-24/LTE-B"]}, + {name: "CRC-24/OPENPGP", algo: crc, params: ["CRC-24/OPENPGP"]}, + {name: "CRC-24/OS-9", algo: crc, params: ["CRC-24/OS-9"]}, + {name: "CRC-30/CDMA", algo: crc, params: ["CRC-30/CDMA"]}, + {name: "CRC-31/PHILIPS", algo: crc, params: ["CRC-31/PHILIPS"]}, + {name: "Adler-32", algo: adler32, params: []}, + {name: "CRC-32", algo: crc, params: ["CRC-32"]}, + {name: "CRC-32/AAL5", algo: crc, params: ["CRC-32/AAL5"]}, + {name: "CRC-32/ADCCP", algo: crc, params: ["CRC-32/ADCCP"]}, + {name: "CRC-32/AIXM", algo: crc, params: ["CRC-32/AIXM"]}, + {name: "CRC-32/AUTOSAR", algo: crc, params: ["CRC-32/AUTOSAR"]}, + {name: "CRC-32/BASE91-C", algo: crc, params: ["CRC-32/BASE91-C"]}, + {name: "CRC-32/BASE91-D", algo: crc, params: ["CRC-32/BASE91-D"]}, + {name: "CRC-32/BZIP2", algo: crc, params: ["CRC-32/BZIP2"]}, + {name: "CRC-32/C", algo: crc, params: ["CRC-32/C"]}, + {name: "CRC-32/CASTAGNOLI", algo: crc, params: ["CRC-32/CASTAGNOLI"]}, + {name: "CRC-32/CD-ROM-EDC", algo: crc, params: ["CRC-32/CD-ROM-EDC"]}, + {name: "CRC-32/CKSUM", algo: crc, params: ["CRC-32/CKSUM"]}, + {name: "CRC-32/D", algo: crc, params: ["CRC-32/D"]}, + {name: "CRC-32/DECT-B", algo: crc, params: ["CRC-32/DECT-B"]}, + {name: "CRC-32/INTERLAKEN", algo: crc, params: ["CRC-32/INTERLAKEN"]}, + {name: "CRC-32/ISCSI", algo: crc, params: ["CRC-32/ISCSI"]}, + {name: "CRC-32/ISO-HDLC", algo: crc, params: ["CRC-32/ISO-HDLC"]}, + {name: "CRC-32/JAMCRC", algo: crc, params: ["CRC-32/JAMCRC"]}, + {name: "CRC-32/MEF", algo: crc, params: ["CRC-32/MEF"]}, + {name: "CRC-32/MPEG-2", algo: crc, params: ["CRC-32/MPEG-2"]}, + {name: "CRC-32/NVME", algo: crc, params: ["CRC-32/NVME"]}, + {name: "CRC-32/PKZIP", algo: crc, params: ["CRC-32/PKZIP"]}, + {name: "CRC-32/POSIX", algo: crc, params: ["CRC-32/POSIX"]}, + {name: "CRC-32/Q", algo: crc, params: ["CRC-32/Q"]}, + {name: "CRC-32/SATA", algo: crc, params: ["CRC-32/SATA"]}, + {name: "CRC-32/V-42", algo: crc, params: ["CRC-32/V-42"]}, + {name: "CRC-32/XFER", algo: crc, params: ["CRC-32/XFER"]}, + {name: "CRC-32/XZ", algo: crc, params: ["CRC-32/XZ"]}, + {name: "Fletcher-32", algo: fletcher32, params: []}, + {name: "CRC-40/GSM", algo: crc, params: ["CRC-40/GSM"]}, + {name: "CRC-64/ECMA-182", algo: crc, params: ["CRC-64/ECMA-182"]}, + {name: "CRC-64/GO-ECMA", algo: crc, params: ["CRC-64/GO-ECMA"]}, + {name: "CRC-64/GO-ISO", algo: crc, params: ["CRC-64/GO-ISO"]}, + {name: "CRC-64/MS", algo: crc, params: ["CRC-64/MS"]}, + {name: "CRC-64/NVME", algo: crc, params: ["CRC-64/NVME"]}, + {name: "CRC-64/REDIS", algo: crc, params: ["CRC-64/REDIS"]}, + {name: "CRC-64/WE", algo: crc, params: ["CRC-64/WE"]}, + {name: "CRC-64/XZ", algo: crc, params: ["CRC-64/XZ"]}, + {name: "Fletcher-64", algo: fletcher64, params: []}, + {name: "CRC-82/DARC", algo: crc, params: ["CRC-82/DARC"]} + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [length, includeNames] = args; + let output = ""; + this.checksums.forEach(checksum => { + const checksumLength = checksum.name.match(new RegExp("-(\\d{1,2})(\\/|$)"))[1]; + if (length === "All" || length === checksumLength) { + const value = checksum.algo.run(new Uint8Array(input), checksum.params || []); + output += includeNames ? + `${checksum.name}:${" ".repeat(25-checksum.name.length)}${value}\n`: + `${value}\n`; + } + }); + return output; + } +} + +export default GenerateAllChecksums; diff --git a/src/core/operations/GenerateAllHashes.mjs b/src/core/operations/GenerateAllHashes.mjs index 06b5f7d917..df09aa8581 100644 --- a/src/core/operations/GenerateAllHashes.mjs +++ b/src/core/operations/GenerateAllHashes.mjs @@ -22,12 +22,6 @@ import HAS160 from "./HAS160.mjs"; import Whirlpool from "./Whirlpool.mjs"; import SSDEEP from "./SSDEEP.mjs"; import CTPH from "./CTPH.mjs"; -import Fletcher8Checksum from "./Fletcher8Checksum.mjs"; -import Fletcher16Checksum from "./Fletcher16Checksum.mjs"; -import Fletcher32Checksum from "./Fletcher32Checksum.mjs"; -import Fletcher64Checksum from "./Fletcher64Checksum.mjs"; -import Adler32Checksum from "./Adler32Checksum.mjs"; -import CRCChecksum from "./CRCChecksum.mjs"; import BLAKE2b from "./BLAKE2b.mjs"; import BLAKE2s from "./BLAKE2s.mjs"; import Streebog from "./Streebog.mjs"; @@ -112,16 +106,6 @@ class GenerateAllHashes extends Operation { {name: "SSDEEP", algo: (new SSDEEP()), inputType: "str"}, {name: "CTPH", algo: (new CTPH()), inputType: "str"} ]; - this.checksums = [ - {name: "Fletcher-8", algo: (new Fletcher8Checksum), inputType: "byteArray", params: []}, - {name: "Fletcher-16", algo: (new Fletcher16Checksum), inputType: "byteArray", params: []}, - {name: "Fletcher-32", algo: (new Fletcher32Checksum), inputType: "byteArray", params: []}, - {name: "Fletcher-64", algo: (new Fletcher64Checksum), inputType: "byteArray", params: []}, - {name: "Adler-32", algo: (new Adler32Checksum), inputType: "byteArray", params: []}, - {name: "CRC-8", algo: (new CRCChecksum), inputType: "arrayBuffer", params: ["CRC-8"]}, - {name: "CRC-16", algo: (new CRCChecksum), inputType: "arrayBuffer", params: ["CRC-16"]}, - {name: "CRC-32", algo: (new CRCChecksum), inputType: "arrayBuffer", params: ["CRC-32"]} - ]; } /** @@ -142,14 +126,6 @@ class GenerateAllHashes extends Operation { output += this.formatDigest(digest, length, includeNames, hash.name); }); - if (length === "All") { - output += "\nChecksums:\n"; - this.checksums.forEach(checksum => { - digest = this.executeAlgo(checksum.algo, checksum.inputType, checksum.params || []); - output += this.formatDigest(digest, length, includeNames, checksum.name); - }); - } - return output; } diff --git a/src/core/operations/GenerateUUID.mjs b/src/core/operations/GenerateUUID.mjs index 1ee0faba6e..21d063e3e0 100644 --- a/src/core/operations/GenerateUUID.mjs +++ b/src/core/operations/GenerateUUID.mjs @@ -5,8 +5,8 @@ */ import Operation from "../Operation.mjs"; -import crypto from "crypto"; - +import * as uuid from "uuid"; +import OperationError from "../errors/OperationError.mjs"; /** * Generate UUID operation */ @@ -20,11 +20,38 @@ class GenerateUUID extends Operation { this.name = "Generate UUID"; this.module = "Crypto"; - this.description = "Generates an RFC 4122 version 4 compliant Universally Unique Identifier (UUID), also known as a Globally Unique Identifier (GUID).

A version 4 UUID relies on random numbers, in this case generated using window.crypto if available and falling back to Math.random if not."; + this.description = + "Generates an RFC 9562 (formerly RFC 4122) compliant Universally Unique Identifier (UUID), " + + "also known as a Globally Unique Identifier (GUID).
" + + "
" + + "We currently support generating the following UUID versions:
" + + "" + + "UUIDs are generated using the uuid package.
"; this.infoURL = "https://wikipedia.org/wiki/Universally_unique_identifier"; this.inputType = "string"; this.outputType = "string"; - this.args = []; + this.args = [ + { + name: "Version", + hint: "UUID version", + type: "option", + value: ["v1", "v3", "v4", "v5", "v6", "v7"], + defaultIndex: 2, + }, + { + name: "Namespace", + hint: "UUID namespace (UUID; valid for v3 and v5)", + type: "string", + value: "1b671a64-40d5-491e-99b0-da01ff1f3341" + } + ]; } /** @@ -33,16 +60,17 @@ class GenerateUUID extends Operation { * @returns {string} */ run(input, args) { - const buf = new Uint32Array(4).map(() => { - return crypto.randomBytes(4).readUInt32BE(0, true); - }); - let i = 0; - return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { - const r = (buf[i >> 3] >> ((i % 8) * 4)) & 0xf, - v = c === "x" ? r : (r & 0x3 | 0x8); - i++; - return v.toString(16); - }); + const [version, namespace] = args; + const hasDesiredVersion = typeof uuid[version] === "function"; + if (!hasDesiredVersion) throw new OperationError("Invalid UUID version"); + + const requiresNamespace = ["v3", "v5"].includes(version); + if (!requiresNamespace) return uuid[version](); + + const hasValidNamespace = typeof namespace === "string" && uuid.validate(namespace); + if (!hasValidNamespace) throw new OperationError("Invalid UUID namespace"); + + return uuid[version](input, namespace); } } diff --git a/src/core/operations/Jsonata.mjs b/src/core/operations/Jsonata.mjs new file mode 100644 index 0000000000..82cc4d3976 --- /dev/null +++ b/src/core/operations/Jsonata.mjs @@ -0,0 +1,65 @@ +/** + * @author Jon K (jon@ajarsoftware.com) + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import jsonata from "jsonata"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Jsonata Query operation + */ +class JsonataQuery extends Operation { + /** + * JsonataQuery constructor + */ + constructor() { + super(); + + this.name = "Jsonata Query"; + this.module = "Code"; + this.description = + "Query and transform JSON data with a jsonata query."; + this.infoURL = "https://docs.jsonata.org/overview.html"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Query", + type: "text", + value: "string", + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [query] = args; + let result, jsonObj; + + try { + jsonObj = JSON.parse(input); + } catch (err) { + throw new OperationError(`Invalid input JSON: ${err.message}`); + } + + try { + const expression = jsonata(query); + result = await expression.evaluate(jsonObj); + } catch (err) { + throw new OperationError( + `Invalid Jsonata Expression: ${err.message}` + ); + } + + return JSON.stringify(result === undefined ? "" : result); + } +} + +export default JsonataQuery; diff --git a/src/core/operations/RailFenceCipherDecode.mjs b/src/core/operations/RailFenceCipherDecode.mjs index be54ee1247..39795f2150 100644 --- a/src/core/operations/RailFenceCipherDecode.mjs +++ b/src/core/operations/RailFenceCipherDecode.mjs @@ -72,7 +72,7 @@ class RailFenceCipherDecode extends Operation { } } - return plaintext.join("").trim(); + return plaintext.join(""); } } diff --git a/src/core/operations/RailFenceCipherEncode.mjs b/src/core/operations/RailFenceCipherEncode.mjs index 03651f8573..89eddde752 100644 --- a/src/core/operations/RailFenceCipherEncode.mjs +++ b/src/core/operations/RailFenceCipherEncode.mjs @@ -66,7 +66,7 @@ class RailFenceCipherEncode extends Operation { rows[rowIdx] += plaintext[pos]; } - return rows.join("").trim(); + return rows.join(""); } } diff --git a/src/core/operations/ShowOnMap.mjs b/src/core/operations/ShowOnMap.mjs index c2ac1c6e3d..d75c2aa6a9 100644 --- a/src/core/operations/ShowOnMap.mjs +++ b/src/core/operations/ShowOnMap.mjs @@ -1,6 +1,7 @@ /** * @author j433866 [j433866@gmail.com] - * @copyright Crown Copyright 2019 + * @author 0xff1ce [github.com/0xff1ce] + * @copyright Crown Copyright 2024 * @license Apache-2.0 */ @@ -22,7 +23,7 @@ class ShowOnMap extends Operation { this.name = "Show on map"; this.module = "Hashing"; this.description = "Displays co-ordinates on a slippy map.

Co-ordinates will be converted to decimal degrees before being shown on the map.

Supported formats:
This operation will not work offline."; - this.infoURL = "https://foundation.wikimedia.org/wiki/Maps_Terms_of_Use"; + this.infoURL = "https://osmfoundation.org/wiki/Terms_of_Use"; this.inputType = "string"; this.outputType = "string"; this.presentType = "html"; @@ -85,10 +86,10 @@ class ShowOnMap extends Operation { data = "0, 0"; } const zoomLevel = args[0]; - const tileUrl = "https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png", - tileAttribution = "
Wikimedia maps | © OpenStreetMap contributors", - leafletUrl = "https://unpkg.com/leaflet@1.5.0/dist/leaflet.js", - leafletCssUrl = "https://unpkg.com/leaflet@1.5.0/dist/leaflet.css"; + const tileUrl = "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + tileAttribution = "© OpenStreetMap contributors", + leafletUrl = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.js", + leafletCssUrl = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"; return `