diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..dd87e2d7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +node_modules +build diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index b485edad..daa59490 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -33,6 +33,20 @@ jobs: 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: | diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index b1be78f9..a068ffbb 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -6,6 +6,12 @@ on: tags: - 'v*' +env: + REGISTRY: ghcr.io + REGISTRY_USER: ${{ github.actor }} + REGISTRY_PASSWORD: ${{ github.token }} + IMAGE_NAME: ${{ github.repository }} + jobs: main: runs-on: ubuntu-latest @@ -19,7 +25,7 @@ jobs: - name: Install run: | - npm install + npm ci npm run setheapsize - name: Lint @@ -31,17 +37,38 @@ jobs: npm run testnodeconsumer - name: Production Build - if: success() run: npx grunt prod - name: UI Tests - if: success() 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 + oci: true + # Webpack seems to use a lot of open files, increase the max open file limit to accomodate. + extra-args: | + --ulimit nofile=10000 + + - name: Upload Release Assets - if: success() id: upload-release-assets uses: svenstaro/upload-release-action@v2 with: @@ -53,7 +80,14 @@ jobs: body: "See the [CHANGELOG](https://github.com/gchq/CyberChef/blob/master/CHANGELOG.md) and [commit messages](https://github.com/gchq/CyberChef/commits/master) for details." - name: Publish to NPM - if: success() uses: JS-DevTools/npm-publish@v1 with: token: ${{ secrets.NPM_TOKEN }} + + - name: Publish to GHCR + uses: redhat-actions/push-to-registry@v2 + with: + tags: ${{ steps.build-image.outputs.tags }} + registry: ${{ env.REGISTRY }} + username: ${{ env.REGISTRY_USER }} + password: ${{ env.REGISTRY_PASSWORD }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dabea66..145d1b14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ All major and minor version changes will be documented in this file. Details of ## Details +### [10.8.0] - 2024-02-13 +- Add official Docker images [@AshCorr] | [#1699] + ### [10.7.0] - 2024-02-09 - Added 'File Tree' operation [@sw5678] | [#1667] - Added 'RISON' operation [@sg5506844] | [#1555] @@ -383,6 +386,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.8.0]: https://github.com/gchq/CyberChef/releases/tag/v10.7.0 [10.7.0]: https://github.com/gchq/CyberChef/releases/tag/v10.7.0 [10.6.0]: https://github.com/gchq/CyberChef/releases/tag/v10.6.0 [10.5.0]: https://github.com/gchq/CyberChef/releases/tag/v10.5.0 @@ -546,6 +550,7 @@ All major and minor version changes will be documented in this file. Details of [@sw5678]: https://github.com/sw5678 [@sg5506844]: https://github.com/sg5506844 [@AliceGrey]: https://github.com/AliceGrey +[@AshCorr]: https://github.com/AshCorr [8ad18b]: https://github.com/gchq/CyberChef/commit/8ad18bc7db6d9ff184ba3518686293a7685bf7b7 @@ -672,3 +677,4 @@ All major and minor version changes will be documented in this file. Details of [#1667]: https://github.com/gchq/CyberChef/issues/1667 [#1555]: https://github.com/gchq/CyberChef/issues/1555 [#1694]: https://github.com/gchq/CyberChef/issues/1694 +[#1699]: https://github.com/gchq/CyberChef/issues/1694 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..be4c8bad --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM node:18-alpine AS build + +COPY . . +RUN npm ci +RUN npm run build + +FROM nginx:1.25-alpine3.18 AS cyberchef + +COPY --from=build ./build/prod /usr/share/nginx/html/ diff --git a/README.md b/README.md index 24f56e77..5549bda2 100755 --- a/README.md +++ b/README.md @@ -20,6 +20,22 @@ Cryptographic operations in CyberChef should not be relied upon to provide secur [A live demo can be found here][1] - have fun! +## Containers + +If you would like to try out CyberChef locally you can either build it yourself: + +```bash +docker build --tag cyberchef --ulimit nofile=10000 . +docker run -it -p 8080:80 cyberchef +``` + +Or you can use our image directly: + +```bash +docker run -it -p 8080:80 ghcr.io/gchq/cyberchef:latest +``` + +This image is built and published through our [GitHub Workflows](.github/workflows/releases.yml) ## How it works diff --git a/package-lock.json b/package-lock.json index 1d1c6333..29717727 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "10.7.0", + "version": "10.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "10.7.0", + "version": "10.8.0", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 0ab4990e..e0c3b136 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "10.7.0", + "version": "10.8.0", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", diff --git a/src/core/operations/Diff.mjs b/src/core/operations/Diff.mjs index 84107245..9f180f86 100644 --- a/src/core/operations/Diff.mjs +++ b/src/core/operations/Diff.mjs @@ -119,9 +119,9 @@ class Diff extends Operation { for (let i = 0; i < diff.length; i++) { if (diff[i].added) { - if (showAdded) output += "" + Utils.escapeHtml(diff[i].value) + ""; + if (showAdded) output += "" + Utils.escapeHtml(diff[i].value) + ""; } else if (diff[i].removed) { - if (showRemoved) output += "" + Utils.escapeHtml(diff[i].value) + ""; + if (showRemoved) output += "" + Utils.escapeHtml(diff[i].value) + ""; } else if (!showSubtraction) { output += Utils.escapeHtml(diff[i].value); } diff --git a/src/web/stylesheets/index.css b/src/web/stylesheets/index.css index 960c7006..0565399f 100755 --- a/src/web/stylesheets/index.css +++ b/src/web/stylesheets/index.css @@ -36,4 +36,5 @@ @import "./layout/_structure.css"; /* Operations */ +@import "./operations/diff.css"; @import "./operations/json.css"; diff --git a/src/web/stylesheets/operations/diff.css b/src/web/stylesheets/operations/diff.css new file mode 100644 index 00000000..008cbbf5 --- /dev/null +++ b/src/web/stylesheets/operations/diff.css @@ -0,0 +1,8 @@ +del { + background-color: var(--hl3); +} + +ins { + text-decoration: underline; /* shouldn't be needed, but Chromium doesn't copy to clipboard without it */ + background-color: var(--hl5); +} diff --git a/tests/browser/02_ops.js b/tests/browser/02_ops.js index e2c8a219..7cb0e941 100644 --- a/tests/browser/02_ops.js +++ b/tests/browser/02_ops.js @@ -108,7 +108,7 @@ module.exports = { // testOp(browser, "Derive EVP key", "test input", "test_output"); // testOp(browser, "Derive PBKDF2 key", "test input", "test_output"); // testOp(browser, "Detect File Type", "test input", "test_output"); - testOpHtml(browser, "Diff", "The cat sat on the mat\n\nThe mat cat on the sat", ".hl5:first-child", "mat", ["\\n\\n", "Word", true, true, false, false]); + testOpHtml(browser, "Diff", "The cat sat on the mat\n\nThe mat cat on the sat", "ins:first-child", "mat", ["\\n\\n", "Word", true, true, false, false]); // testOp(browser, "Disassemble x86", "test input", "test_output"); testOpImage(browser, "Dither Image", "files/Hitchhikers_Guide.jpeg"); // testOp(browser, "Divide", "test input", "test_output"); diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 1197104b..98374650 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -17,132 +17,130 @@ import { } from "../lib/utils.mjs"; import TestRegister from "../lib/TestRegister.mjs"; -import "./tests/BCD.mjs"; -import "./tests/BSON.mjs"; +import "./tests/AESKeyWrap.mjs"; +import "./tests/AvroToJSON.mjs"; import "./tests/BaconCipher.mjs"; import "./tests/Base45.mjs"; import "./tests/Base58.mjs"; -import "./tests/Base64.mjs"; import "./tests/Base62.mjs"; +import "./tests/Base64.mjs"; import "./tests/Base85.mjs"; import "./tests/Base92.mjs"; +import "./tests/BCD.mjs"; import "./tests/BitwiseOp.mjs"; +import "./tests/BLAKE2b.mjs"; +import "./tests/BLAKE2s.mjs"; +import "./tests/Bombe.mjs"; +import "./tests/BSON.mjs"; import "./tests/ByteRepr.mjs"; +import "./tests/CaesarBoxCipher.mjs"; import "./tests/CartesianProduct.mjs"; -import "./tests/CetaceanCipherEncode.mjs"; +import "./tests/CBORDecode.mjs"; +import "./tests/CBOREncode.mjs"; import "./tests/CetaceanCipherDecode.mjs"; +import "./tests/CetaceanCipherEncode.mjs"; import "./tests/ChaCha.mjs"; -import "./tests/CharEnc.mjs"; import "./tests/ChangeIPFormat.mjs"; +import "./tests/CharEnc.mjs"; import "./tests/Charts.mjs"; import "./tests/Checksum.mjs"; import "./tests/Ciphers.mjs"; +import "./tests/CipherSaber2.mjs"; +import "./tests/CMAC.mjs"; import "./tests/Code.mjs"; +import "./tests/Colossus.mjs"; import "./tests/Comment.mjs"; import "./tests/Compress.mjs"; import "./tests/ConditionalJump.mjs"; +import "./tests/ConvertCoordinateFormat.mjs"; +import "./tests/ConvertToNATOAlphabet.mjs"; import "./tests/Crypt.mjs"; import "./tests/CSV.mjs"; import "./tests/DateTime.mjs"; +import "./tests/DefangIP.mjs"; +import "./tests/ELFInfo.mjs"; +import "./tests/Enigma.mjs"; import "./tests/ExtractEmailAddresses.mjs"; +import "./tests/FileTree.mjs"; +import "./tests/FletcherChecksum.mjs"; import "./tests/Fork.mjs"; import "./tests/FromDecimal.mjs"; import "./tests/GenerateAllHashes.mjs"; -import "./tests/Gzip.mjs"; +import "./tests/GenerateDeBruijnSequence.mjs"; +import "./tests/GetAllCasings.mjs"; +import "./tests/GOST.mjs"; import "./tests/Gunzip.mjs"; +import "./tests/Gzip.mjs"; import "./tests/Hash.mjs"; +import "./tests/HASSH.mjs"; import "./tests/HaversineDistance.mjs"; import "./tests/Hex.mjs"; import "./tests/Hexdump.mjs"; +import "./tests/HKDF.mjs"; import "./tests/Image.mjs"; import "./tests/IndexOfCoincidence.mjs"; -import "./tests/Jump.mjs"; +import "./tests/JA3Fingerprint.mjs"; +import "./tests/JA3SFingerprint.mjs"; import "./tests/JSONBeautify.mjs"; import "./tests/JSONMinify.mjs"; import "./tests/JSONtoCSV.mjs"; +import "./tests/Jump.mjs"; import "./tests/JWTDecode.mjs"; import "./tests/JWTSign.mjs"; import "./tests/JWTVerify.mjs"; +import "./tests/LevenshteinDistance.mjs"; +import "./tests/Lorenz.mjs"; +import "./tests/LS47.mjs"; +import "./tests/LuhnChecksum.mjs"; import "./tests/LZNT1Decompress.mjs"; -import "./tests/MS.mjs"; +import "./tests/LZString.mjs"; import "./tests/Magic.mjs"; +import "./tests/Media.mjs"; import "./tests/MorseCode.mjs"; +import "./tests/MS.mjs"; +import "./tests/MultipleBombe.mjs"; import "./tests/MurmurHash3.mjs"; import "./tests/NetBIOS.mjs"; import "./tests/NormaliseUnicode.mjs"; +import "./tests/NTLM.mjs"; import "./tests/OTP.mjs"; +import "./tests/ParseIPRange.mjs"; +import "./tests/ParseObjectIDTimestamp.mjs"; +import "./tests/ParseQRCode.mjs"; +import "./tests/ParseSSHHostKey.mjs"; +import "./tests/ParseTCP.mjs"; +import "./tests/ParseTLV.mjs"; +import "./tests/ParseUDP.mjs"; +import "./tests/PEMtoHex.mjs"; import "./tests/PGP.mjs"; import "./tests/PHP.mjs"; -import "./tests/ParseIPRange.mjs"; -import "./tests/ParseQRCode.mjs"; -import "./tests/PEMtoHex.mjs"; import "./tests/PowerSet.mjs"; -import "./tests/RAKE.mjs"; +import "./tests/Protobuf.mjs"; +import "./tests/Rabbit.mjs"; import "./tests/Regex.mjs"; import "./tests/Register.mjs"; +import "./tests/RisonEncodeDecode.mjs"; import "./tests/Rotate.mjs"; +import "./tests/RSA.mjs"; import "./tests/SeqUtils.mjs"; import "./tests/SetDifference.mjs"; import "./tests/SetIntersection.mjs"; import "./tests/SetUnion.mjs"; +import "./tests/Shuffle.mjs"; +import "./tests/SIGABA.mjs"; import "./tests/SM4.mjs"; +// import "./tests/SplitColourChannels.mjs"; // Cannot test operations that use the File type yet import "./tests/StrUtils.mjs"; +import "./tests/Subsection.mjs"; +import "./tests/SwapCase.mjs"; import "./tests/SymmetricDifference.mjs"; import "./tests/TextEncodingBruteForce.mjs"; -import "./tests/TranslateDateTimeFormat.mjs"; -import "./tests/Magic.mjs"; -import "./tests/ParseTLV.mjs"; -import "./tests/Media.mjs"; import "./tests/ToFromInsensitiveRegex.mjs"; -import "./tests/YARA.mjs"; -import "./tests/ConvertCoordinateFormat.mjs"; -import "./tests/Enigma.mjs"; -import "./tests/Bombe.mjs"; -import "./tests/MultipleBombe.mjs"; +import "./tests/TranslateDateTimeFormat.mjs"; import "./tests/Typex.mjs"; -import "./tests/BLAKE2b.mjs"; -import "./tests/BLAKE2s.mjs"; -import "./tests/Protobuf.mjs"; -import "./tests/ParseSSHHostKey.mjs"; -import "./tests/DefangIP.mjs"; -import "./tests/ParseUDP.mjs"; -import "./tests/ParseTCP.mjs"; -import "./tests/AvroToJSON.mjs"; -import "./tests/Lorenz.mjs"; -import "./tests/LuhnChecksum.mjs"; -import "./tests/CipherSaber2.mjs"; -import "./tests/Colossus.mjs"; -import "./tests/ParseObjectIDTimestamp.mjs"; -import "./tests/Unicode.mjs"; -import "./tests/RSA.mjs"; -import "./tests/CBOREncode.mjs"; -import "./tests/CBORDecode.mjs"; -import "./tests/RisonEncodeDecode.mjs"; -import "./tests/JA3Fingerprint.mjs"; -import "./tests/JA3SFingerprint.mjs"; -import "./tests/HASSH.mjs"; -import "./tests/GetAllCasings.mjs"; -import "./tests/SIGABA.mjs"; -import "./tests/ELFInfo.mjs"; -import "./tests/Subsection.mjs"; -import "./tests/CaesarBoxCipher.mjs"; import "./tests/UnescapeString.mjs"; -import "./tests/LS47.mjs"; -import "./tests/LZString.mjs"; -import "./tests/NTLM.mjs"; -import "./tests/Shuffle.mjs"; -import "./tests/FletcherChecksum.mjs"; -import "./tests/CMAC.mjs"; -import "./tests/AESKeyWrap.mjs"; -import "./tests/Rabbit.mjs"; -import "./tests/LevenshteinDistance.mjs"; -import "./tests/SwapCase.mjs"; -import "./tests/HKDF.mjs"; -import "./tests/GenerateDeBruijnSequence.mjs"; -import "./tests/GOST.mjs"; - -// Cannot test operations that use the File type yet -// import "./tests/SplitColourChannels.mjs"; +import "./tests/Unicode.mjs"; +import "./tests/YARA.mjs"; const testStatus = { allTestsPassing: true, diff --git a/tests/operations/tests/FileTree.mjs b/tests/operations/tests/FileTree.mjs index 34249e3c..fc97678f 100644 --- a/tests/operations/tests/FileTree.mjs +++ b/tests/operations/tests/FileTree.mjs @@ -1,4 +1,6 @@ /** + * File tree tests. + * * @author sw5678 * @copyright Crown Copyright 2023 * @license Apache-2.0 @@ -7,14 +9,13 @@ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { - "name": "Swap Case: basic example", + "name": "File Tree: basic example", "input": "/test_dir1/test_file1.txt\n/test_dir1/test_file2.txt\n/test_dir2/test_file1.txt", "expectedOutput": "test_dir1\n|---test_file1.txt\n|---test_file2.txt\ntest_dir2\n|---test_file1.txt", "recipeConfig": [ { "op": "File Tree", - "args": [ - ], + "args": ["/", "Line feed"], }, ], } diff --git a/tests/operations/tests/FromGeohash.mjs b/tests/operations/tests/FromGeohash.mjs deleted file mode 100644 index dec58687..00000000 --- a/tests/operations/tests/FromGeohash.mjs +++ /dev/null @@ -1,55 +0,0 @@ -/** - * To Geohash tests - * - * @author gchq77703 - * @copyright Crown Copyright 2018 - * @license Apache-2.0 - */ -import TestRegister from "../../lib/TestRegister.mjs"; - -TestRegister.addTests([ - { - name: "From Geohash", - input: "ww8p1r4t8", - expectedOutput: "37.83238649368286,112.55838632583618", - recipeConfig: [ - { - op: "From Geohash", - args: [], - }, - ], - }, - { - name: "From Geohash", - input: "ww8p1r", - expectedOutput: "37.83416748046875,112.5604248046875", - recipeConfig: [ - { - op: "From Geohash", - args: [], - }, - ], - }, - { - name: "From Geohash", - input: "ww8", - expectedOutput: "37.265625,113.203125", - recipeConfig: [ - { - op: "From Geohash", - args: [], - }, - ], - }, - { - name: "From Geohash", - input: "w", - expectedOutput: "22.5,112.5", - recipeConfig: [ - { - op: "From Geohash", - args: [], - }, - ], - }, -]); diff --git a/tests/operations/tests/StrUtils.mjs b/tests/operations/tests/StrUtils.mjs index c78b6b23..70e9c362 100644 --- a/tests/operations/tests/StrUtils.mjs +++ b/tests/operations/tests/StrUtils.mjs @@ -11,7 +11,7 @@ TestRegister.addTests([ { name: "Diff, basic usage", input: "testing23\n\ntesting123", - expectedOutput: "testing123", + expectedOutput: "testing123", recipeConfig: [ { "op": "Diff", @@ -22,7 +22,7 @@ TestRegister.addTests([ { name: "Diff added with subtraction, basic usage", input: "testing23\n\ntesting123", - expectedOutput: "1", + expectedOutput: "1", recipeConfig: [ { "op": "Diff", @@ -33,7 +33,7 @@ TestRegister.addTests([ { name: "Diff removed with subtraction, basic usage", input: "testing123\n\ntesting3", - expectedOutput: "12", + expectedOutput: "12", recipeConfig: [ { "op": "Diff", diff --git a/tests/operations/tests/ToGeohash.mjs b/tests/operations/tests/ToGeohash.mjs deleted file mode 100644 index 96dece85..00000000 --- a/tests/operations/tests/ToGeohash.mjs +++ /dev/null @@ -1,55 +0,0 @@ -/** - * To Geohash tests - * - * @author gchq77703 - * @copyright Crown Copyright 2018 - * @license Apache-2.0 - */ -import TestRegister from "../../lib/TestRegister.mjs"; - -TestRegister.addTests([ - { - name: "To Geohash", - input: "37.8324,112.5584", - expectedOutput: "ww8p1r4t8", - recipeConfig: [ - { - op: "To Geohash", - args: [9], - }, - ], - }, - { - name: "To Geohash", - input: "37.9324,-112.2584", - expectedOutput: "9w8pv3ruj", - recipeConfig: [ - { - op: "To Geohash", - args: [9], - }, - ], - }, - { - name: "To Geohash", - input: "37.8324,112.5584", - expectedOutput: "ww8", - recipeConfig: [ - { - op: "To Geohash", - args: [3], - }, - ], - }, - { - name: "To Geohash", - input: "37.9324,-112.2584", - expectedOutput: "9w8pv3rujxy5b99", - recipeConfig: [ - { - op: "To Geohash", - args: [15], - }, - ], - }, -]);