mirror of
https://github.com/gchq/CyberChef.git
synced 2025-04-20 06:55:08 -04:00
Compare commits
226 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7c8be12d52 | ||
![]() |
e849569b10 | ||
![]() |
fa559fdbed | ||
![]() |
74d631c71e | ||
![]() |
305a02c8a2 | ||
![]() |
5a3b7bc4c0 | ||
![]() |
c83e1ac4fb | ||
![]() |
857576dbe4 | ||
![]() |
e00a636fc0 | ||
![]() |
6f4cbd8688 | ||
![]() |
848660f8e1 | ||
![]() |
0e7b1f7c78 | ||
![]() |
bbb211d819 | ||
![]() |
9ca0152aac | ||
![]() |
4b72484ca4 | ||
![]() |
e469f00fc7 | ||
![]() |
75e3f35932 | ||
![]() |
9a8804ab94 | ||
![]() |
f5b5227e53 | ||
![]() |
14472d3b40 | ||
![]() |
9f30dd0e7a | ||
![]() |
49d69a293b | ||
![]() |
23de98f892 | ||
![]() |
8852712f0d | ||
![]() |
b62def3d3e | ||
![]() |
464c92e7bf | ||
![]() |
95044ab767 | ||
![]() |
a809321b63 | ||
![]() |
b6f0f9b200 | ||
![]() |
a42c7de112 | ||
![]() |
b85036b78f | ||
![]() |
3b75e13287 | ||
![]() |
98ef5d0110 | ||
![]() |
2c21d72a6a | ||
![]() |
42efc0187c | ||
![]() |
d5f007deea | ||
![]() |
566b423065 | ||
![]() |
1b4760c447 | ||
![]() |
3025391821 | ||
![]() |
4e62aa6e1d | ||
![]() |
fcecd029c7 | ||
![]() |
3057a20791 | ||
![]() |
b2981d3cc7 | ||
![]() |
d3357d2acd | ||
![]() |
7babef6f93 | ||
![]() |
a40aed2b47 | ||
![]() |
5cef2b13a3 | ||
![]() |
c2936a6f2c | ||
![]() |
06b7f0129f | ||
![]() |
e44e6fed35 | ||
![]() |
a74e2e585e | ||
![]() |
d602897221 | ||
![]() |
1b8c229df2 | ||
![]() |
aa7bbc1a62 | ||
![]() |
6ccbc892c7 | ||
![]() |
7b3980a5e2 | ||
![]() |
7906f9d560 | ||
![]() |
2ae923b73e | ||
![]() |
15bbed093c | ||
![]() |
23faeadea2 | ||
![]() |
50c0d4bcd6 | ||
![]() |
7f97afd3e0 | ||
![]() |
c3994aa8e3 | ||
![]() |
8166f981ae | ||
![]() |
1cfbc2babb | ||
![]() |
eb912547ac | ||
![]() |
bcf62ec73d | ||
![]() |
004ef7f5dc | ||
![]() |
9ab990ff59 | ||
![]() |
fb8e1be9f0 | ||
![]() |
324560aeae | ||
![]() |
95c6406d05 | ||
![]() |
51b9fe644b | ||
![]() |
fed10f3ee6 | ||
![]() |
7cc3e5804b | ||
![]() |
dcf0bbb02b | ||
![]() |
f8b613b4e7 | ||
![]() |
e48fc4c8d8 | ||
![]() |
1b53b8d23f | ||
![]() |
355566736a | ||
![]() |
b99a76f9a6 | ||
![]() |
912bbe54aa | ||
![]() |
26a16b6945 | ||
![]() |
feaf3dd119 | ||
![]() |
3187ff6322 | ||
![]() |
13f94a23f8 | ||
![]() |
592e660deb | ||
![]() |
3cdbdf4223 | ||
![]() |
8cd875d06a | ||
![]() |
bf36fe8143 | ||
![]() |
d3ee16f38b | ||
![]() |
41c5403700 | ||
![]() |
e1d3af26da | ||
![]() |
5a7cb9e047 | ||
![]() |
a41068e633 | ||
![]() |
bce04609a5 | ||
![]() |
d4da81fa91 | ||
![]() |
d3e31890ae | ||
![]() |
9d014aeaa5 | ||
![]() |
156de53dc1 | ||
![]() |
9f2527d736 | ||
![]() |
7ecc235b31 | ||
![]() |
51ed3b2fc1 | ||
![]() |
306e29da76 | ||
![]() |
9cc84b1c62 | ||
![]() |
f02b3f22ad | ||
![]() |
e9b8163626 | ||
![]() |
20390ae08e | ||
![]() |
fcdcce7ee4 | ||
![]() |
3d017d5f84 | ||
![]() |
5455061e15 | ||
![]() |
caa373c1be | ||
![]() |
f4995dbc30 | ||
![]() |
f14c423c64 | ||
![]() |
6f63de81b1 | ||
![]() |
75991321de | ||
![]() |
b560d74b8e | ||
![]() |
90133e3f90 | ||
![]() |
672cb5dea4 | ||
![]() |
7eb9e08ad2 | ||
![]() |
2007b26d57 | ||
![]() |
e804555278 | ||
![]() |
6b75ba8903 | ||
![]() |
54e559c5a6 | ||
![]() |
cc7cc7f8fd | ||
![]() |
6a92f922cb | ||
![]() |
71c8c8aac0 | ||
![]() |
304266020d | ||
![]() |
02f45e545d | ||
![]() |
3deb121043 | ||
![]() |
bab0a7ffed | ||
![]() |
1fcc365d9e | ||
![]() |
b92c1e7da0 | ||
![]() |
0c6c45b152 | ||
![]() |
802c576c60 | ||
![]() |
3822c6c520 | ||
![]() |
47c85a105d | ||
![]() |
d3adfc7c3e | ||
![]() |
270a333179 | ||
![]() |
895a929925 | ||
![]() |
e484d426fe | ||
![]() |
4c6dcf182d | ||
![]() |
4bb1e335ea | ||
![]() |
ae9054dc37 | ||
![]() |
f61bdf06c6 | ||
![]() |
0f16fa0ce1 | ||
![]() |
84ce8e6f30 | ||
![]() |
9eff9e5018 | ||
![]() |
857d3b6d17 | ||
![]() |
54cfb17145 | ||
![]() |
99ba6b487c | ||
![]() |
a1647b02cb | ||
![]() |
b15322467b | ||
![]() |
9229fe9ac7 | ||
![]() |
488c458720 | ||
![]() |
784b2631a8 | ||
![]() |
3eb1af0278 | ||
![]() |
7e5eb01a5e | ||
![]() |
1fce8e5112 | ||
![]() |
748379f0b0 | ||
![]() |
da74d9b22d | ||
![]() |
738e6feda0 | ||
![]() |
1fde2fba29 | ||
![]() |
a50d4d63eb | ||
![]() |
dbc90090cf | ||
![]() |
e65869a10b | ||
![]() |
7877a0d7d0 | ||
![]() |
d635cca210 | ||
![]() |
0e82e4b7c6 | ||
![]() |
5f88ae44ec | ||
![]() |
7a5225c961 | ||
![]() |
a80b1568ac | ||
![]() |
a616e46b50 | ||
![]() |
752ce789c2 | ||
![]() |
660069865a | ||
![]() |
9169254fff | ||
![]() |
e12b00485e | ||
![]() |
51a07a27db | ||
![]() |
c8c9e572a6 | ||
![]() |
7fb4824cd6 | ||
![]() |
5e763b86dc | ||
![]() |
b090ce0428 | ||
![]() |
3dddb516c9 | ||
![]() |
fb3c36af85 | ||
![]() |
a477f47aec | ||
![]() |
965570d250 | ||
![]() |
ab37c1e562 | ||
![]() |
40fda00db4 | ||
![]() |
d5374454f4 | ||
![]() |
edd22372d8 | ||
![]() |
4c5577ddeb | ||
![]() |
534ab23d9b | ||
![]() |
fe9f4fa7a9 | ||
![]() |
a8b1050d75 | ||
![]() |
e80d3d59bc | ||
![]() |
1efbd9dfd1 | ||
![]() |
6c30c9c6b8 | ||
![]() |
4528a1bdb6 | ||
![]() |
c23a8de5a0 | ||
![]() |
0cd4d41cdc | ||
![]() |
2b275f0897 | ||
![]() |
63913f4d45 | ||
![]() |
b6c95492f1 | ||
![]() |
ae03e34489 | ||
![]() |
7eb887ca51 | ||
![]() |
74d0166682 | ||
![]() |
66d3b6e9ed | ||
![]() |
036f80d734 | ||
![]() |
3ca4c2fd45 | ||
![]() |
cc21fe18ca | ||
![]() |
4255d8d543 | ||
![]() |
7353315baa | ||
![]() |
721061d054 | ||
![]() |
d2da11c79a | ||
![]() |
c0e84dcd50 | ||
![]() |
15b426ebb6 | ||
![]() |
bf1e708a4c | ||
![]() |
23763363ba | ||
![]() |
5001adf221 | ||
![]() |
518b336431 | ||
![]() |
b125f82784 | ||
![]() |
30349dbcb9 | ||
![]() |
02f6537973 | ||
![]() |
502f126986 | ||
![]() |
7c7d1823ca | ||
![]() |
ce6d38860d |
138 changed files with 20073 additions and 4776 deletions
17
.cspell.json
Normal file
17
.cspell.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"version": "0.2",
|
||||
"language": "en,en-gb",
|
||||
"words": [],
|
||||
"dictionaries": [
|
||||
"npm",
|
||||
"softwareTerms",
|
||||
"node",
|
||||
"html",
|
||||
"css",
|
||||
"bash",
|
||||
"en-gb",
|
||||
"misc"
|
||||
],
|
||||
"ignorePaths": ["package.json", "package-lock.json", "node_modules"]
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
src/core/vendor/**
|
116
.eslintrc.json
116
.eslintrc.json
|
@ -1,116 +0,0 @@
|
|||
{
|
||||
"parser": "@babel/eslint-parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2022,
|
||||
"ecmaFeatures": {
|
||||
"impliedStrict": true
|
||||
},
|
||||
"sourceType": "module",
|
||||
"allowImportExportEverywhere": true
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"rules": {
|
||||
// enable additional rules
|
||||
"no-eval": "error",
|
||||
"no-implied-eval": "error",
|
||||
"dot-notation": "error",
|
||||
"eqeqeq": ["error", "smart"],
|
||||
"no-caller": "error",
|
||||
"no-extra-bind": "error",
|
||||
"no-unused-expressions": "error",
|
||||
"no-useless-call": "error",
|
||||
"no-useless-return": "error",
|
||||
"radix": "warn",
|
||||
|
||||
// modify rules from base configurations
|
||||
"no-unused-vars": ["error", {
|
||||
"args": "none",
|
||||
"vars": "all"
|
||||
}],
|
||||
"no-empty": ["error", {
|
||||
"allowEmptyCatch": true
|
||||
}],
|
||||
|
||||
// disable rules from base configurations
|
||||
"no-control-regex": "off",
|
||||
"require-atomic-updates": "off",
|
||||
"no-async-promise-executor": "off",
|
||||
|
||||
// stylistic conventions
|
||||
"brace-style": ["error", "1tbs"],
|
||||
"space-before-blocks": ["error", "always"],
|
||||
"block-spacing": "error",
|
||||
"array-bracket-spacing": "error",
|
||||
"comma-spacing": "error",
|
||||
"spaced-comment": ["error", "always", { "exceptions": ["/"] } ],
|
||||
"comma-style": "error",
|
||||
"computed-property-spacing": "error",
|
||||
"no-trailing-spaces": "warn",
|
||||
"eol-last": "error",
|
||||
"func-call-spacing": "error",
|
||||
"key-spacing": ["warn", {
|
||||
"mode": "minimum"
|
||||
}],
|
||||
"indent": ["error", 4, {
|
||||
"ignoreComments": true,
|
||||
"ArrayExpression": "first",
|
||||
"SwitchCase": 1
|
||||
}],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"quotes": ["error", "double", {
|
||||
"avoidEscape": true,
|
||||
"allowTemplateLiterals": true
|
||||
}],
|
||||
"camelcase": ["error", {
|
||||
"properties": "always"
|
||||
}],
|
||||
"semi": ["error", "always"],
|
||||
"unicode-bom": "error",
|
||||
"require-jsdoc": ["error", {
|
||||
"require": {
|
||||
"FunctionDeclaration": true,
|
||||
"MethodDefinition": true,
|
||||
"ClassDeclaration": true,
|
||||
"ArrowFunctionExpression": true
|
||||
}
|
||||
}],
|
||||
"keyword-spacing": ["error", {
|
||||
"before": true,
|
||||
"after": true
|
||||
}],
|
||||
"no-multiple-empty-lines": ["warn", {
|
||||
"max": 2,
|
||||
"maxEOF": 1,
|
||||
"maxBOF": 0
|
||||
}],
|
||||
"no-whitespace-before-property": "error",
|
||||
"operator-linebreak": ["error", "after"],
|
||||
"space-in-parens": "error",
|
||||
"no-var": "error",
|
||||
"prefer-const": "error"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": "tests/**/*",
|
||||
"rules": {
|
||||
"no-unused-expressions": "off",
|
||||
"no-console": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globals": {
|
||||
"$": false,
|
||||
"jQuery": false,
|
||||
"log": false,
|
||||
"app": false,
|
||||
|
||||
"COMPILE_TIME": false,
|
||||
"COMPILE_MSG": false,
|
||||
"PKG_VERSION": false
|
||||
}
|
||||
}
|
1
.github/workflows/master.yml
vendored
1
.github/workflows/master.yml
vendored
|
@ -19,6 +19,7 @@ jobs:
|
|||
|
||||
- name: Install
|
||||
run: |
|
||||
export DETECT_CHROMEDRIVER_VERSION=true
|
||||
npm install
|
||||
npm run setheapsize
|
||||
|
||||
|
|
1
.github/workflows/pull_requests.yml
vendored
1
.github/workflows/pull_requests.yml
vendored
|
@ -18,6 +18,7 @@ jobs:
|
|||
|
||||
- name: Install
|
||||
run: |
|
||||
export DETECT_CHROMEDRIVER_VERSION=true
|
||||
npm install
|
||||
npm run setheapsize
|
||||
|
||||
|
|
24
.github/workflows/releases.yml
vendored
24
.github/workflows/releases.yml
vendored
|
@ -25,6 +25,7 @@ jobs:
|
|||
|
||||
- name: Install
|
||||
run: |
|
||||
export DETECT_CHROMEDRIVER_VERSION=true
|
||||
npm ci
|
||||
npm run setheapsize
|
||||
|
||||
|
@ -61,12 +62,22 @@ jobs:
|
|||
tags: ${{ steps.image-metadata.outputs.tags }}
|
||||
labels: ${{ steps.image-metadata.outputs.labels }}
|
||||
containerfiles: ./Dockerfile
|
||||
platforms: linux/amd64
|
||||
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
|
||||
|
@ -81,13 +92,6 @@ jobs:
|
|||
|
||||
- name: Publish to NPM
|
||||
uses: JS-DevTools/npm-publish@v1
|
||||
if: false
|
||||
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 }}
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
|
@ -13,6 +13,10 @@ All major and minor version changes will be documented in this file. Details of
|
|||
|
||||
## Details
|
||||
|
||||
### [10.19.0] - 2024-06-21
|
||||
- Add support for ECDSA and DSA in 'Parse CSR' [@robinsandhu] | [#1828]
|
||||
- Fix typos in SIGABA.mjs [@eltociear] | [#1834]
|
||||
|
||||
### [10.18.0] - 2024-04-24
|
||||
- Added 'XXTEA Encrypt' and 'XXTEA Decrypt' operations [@n1474335] | [0a353ee]
|
||||
|
||||
|
@ -436,6 +440,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.19.0]: https://github.com/gchq/CyberChef/releases/tag/v10.19.0
|
||||
[10.18.0]: https://github.com/gchq/CyberChef/releases/tag/v10.18.0
|
||||
[10.17.0]: https://github.com/gchq/CyberChef/releases/tag/v10.17.0
|
||||
[10.16.0]: https://github.com/gchq/CyberChef/releases/tag/v10.16.0
|
||||
|
@ -623,6 +628,8 @@ All major and minor version changes will be documented in this file. Details of
|
|||
[@TheZ3ro]: https://github.com/TheZ3ro
|
||||
[@EvieHarv]: https://github.com/EvieHarv
|
||||
[@cplussharp]: https://github.com/cplussharp
|
||||
[@robinsandhu]: https://github.com/robinsandhu
|
||||
[@eltociear]: https://github.com/eltociear
|
||||
|
||||
|
||||
[8ad18b]: https://github.com/gchq/CyberChef/commit/8ad18bc7db6d9ff184ba3518686293a7685bf7b7
|
||||
|
|
35
Dockerfile
35
Dockerfile
|
@ -1,9 +1,36 @@
|
|||
FROM node:18-alpine AS build
|
||||
#####################################
|
||||
# Build the app to a static website #
|
||||
#####################################
|
||||
# Modifier --platform=$BUILDPLATFORM limits the platform to "BUILDPLATFORM" during buildx multi-platform builds
|
||||
# This is because npm "chromedriver" package is not compatiable with all platforms
|
||||
# For more info see: https://docs.docker.com/build/building/multi-platform/#cross-compilation
|
||||
FROM --platform=$BUILDPLATFORM node:18-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json .
|
||||
COPY package-lock.json .
|
||||
|
||||
# Install dependencies
|
||||
# --ignore-scripts prevents postinstall script (which runs grunt) as it depends on files other than package.json
|
||||
RUN npm ci --ignore-scripts
|
||||
|
||||
# Copy files needed for postinstall and build
|
||||
COPY . .
|
||||
RUN npm ci
|
||||
|
||||
# npm postinstall runs grunt, which depends on files other than package.json
|
||||
RUN npm run postinstall
|
||||
|
||||
# Build the app
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:1.25-alpine3.18 AS cyberchef
|
||||
#########################################
|
||||
# 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
|
||||
|
||||
COPY --from=build ./build/prod /usr/share/nginx/html/
|
||||
COPY --from=builder /app/build/prod /usr/share/nginx/html/
|
||||
|
|
12
Gruntfile.js
12
Gruntfile.js
|
@ -431,6 +431,18 @@ module.exports = function (grunt) {
|
|||
}
|
||||
},
|
||||
stdout: false
|
||||
},
|
||||
fixJimpModule: {
|
||||
command: function () {
|
||||
switch (process.platform) {
|
||||
case "darwin":
|
||||
// Space added before comma to prevent multiple modifications
|
||||
return `sed -i '' 's/"es\\/index.js",/"es\\/index.js" ,\\n "type": "module",/' ./node_modules/jimp/package.json`;
|
||||
default:
|
||||
return `sed -i 's/"es\\/index.js",/"es\\/index.js" ,\\n "type": "module",/' ./node_modules/jimp/package.json`;
|
||||
}
|
||||
},
|
||||
stdout: false
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
129
eslint.config.mjs
Executable file
129
eslint.config.mjs
Executable file
|
@ -0,0 +1,129 @@
|
|||
import babelParser from "@babel/eslint-parser";
|
||||
import jsdoc from "eslint-plugin-jsdoc";
|
||||
import js from "@eslint/js";
|
||||
import globals from "globals";
|
||||
|
||||
export default [
|
||||
js.configs.recommended,
|
||||
{
|
||||
languageOptions: {
|
||||
ecmaVersion: 2022,
|
||||
parser: babelParser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 2022,
|
||||
ecmaFeatures: {
|
||||
impliedStrict: true
|
||||
},
|
||||
sourceType: "module",
|
||||
allowImportExportEverywhere: true
|
||||
},
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node,
|
||||
...globals.es6,
|
||||
"$": false,
|
||||
"jQuery": false,
|
||||
"log": false,
|
||||
"app": false,
|
||||
|
||||
"COMPILE_TIME": false,
|
||||
"COMPILE_MSG": false,
|
||||
"PKG_VERSION": false
|
||||
},
|
||||
},
|
||||
ignores: ["src/core/vendor/**"],
|
||||
plugins: {
|
||||
jsdoc
|
||||
},
|
||||
rules: {
|
||||
// enable additional rules
|
||||
"no-eval": "error",
|
||||
"no-implied-eval": "error",
|
||||
"dot-notation": "error",
|
||||
"eqeqeq": ["error", "smart"],
|
||||
"no-caller": "error",
|
||||
"no-extra-bind": "error",
|
||||
"no-unused-expressions": "error",
|
||||
"no-useless-call": "error",
|
||||
"no-useless-return": "error",
|
||||
"radix": "warn",
|
||||
|
||||
// modify rules from base configurations
|
||||
"no-unused-vars": ["error", {
|
||||
"args": "none",
|
||||
"vars": "all",
|
||||
"caughtErrors": "none"
|
||||
}],
|
||||
"no-empty": ["error", {
|
||||
"allowEmptyCatch": true
|
||||
}],
|
||||
|
||||
// disable rules from base configurations
|
||||
"no-control-regex": "off",
|
||||
"require-atomic-updates": "off",
|
||||
"no-async-promise-executor": "off",
|
||||
|
||||
// stylistic conventions
|
||||
"brace-style": ["error", "1tbs"],
|
||||
"space-before-blocks": ["error", "always"],
|
||||
"block-spacing": "error",
|
||||
"array-bracket-spacing": "error",
|
||||
"comma-spacing": "error",
|
||||
"spaced-comment": ["error", "always", { "exceptions": ["/"] }],
|
||||
"comma-style": "error",
|
||||
"computed-property-spacing": "error",
|
||||
"no-trailing-spaces": "warn",
|
||||
"eol-last": "error",
|
||||
"func-call-spacing": "error",
|
||||
"key-spacing": ["warn", {
|
||||
"mode": "minimum"
|
||||
}],
|
||||
"indent": ["error", 4, {
|
||||
"ignoreComments": true,
|
||||
"ArrayExpression": "first",
|
||||
"SwitchCase": 1
|
||||
}],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"quotes": ["error", "double", {
|
||||
"avoidEscape": true,
|
||||
"allowTemplateLiterals": true
|
||||
}],
|
||||
"camelcase": ["error", {
|
||||
"properties": "always"
|
||||
}],
|
||||
"semi": ["error", "always"],
|
||||
"unicode-bom": "error",
|
||||
"jsdoc/require-jsdoc": ["error", {
|
||||
"require": {
|
||||
"FunctionDeclaration": true,
|
||||
"MethodDefinition": true,
|
||||
"ClassDeclaration": true,
|
||||
"ArrowFunctionExpression": false
|
||||
}
|
||||
}],
|
||||
"keyword-spacing": ["error", {
|
||||
"before": true,
|
||||
"after": true
|
||||
}],
|
||||
"no-multiple-empty-lines": ["warn", {
|
||||
"max": 2,
|
||||
"maxEOF": 1,
|
||||
"maxBOF": 0
|
||||
}],
|
||||
"no-whitespace-before-property": "error",
|
||||
"operator-linebreak": ["error", "after"],
|
||||
"space-in-parens": "error",
|
||||
"no-var": "error",
|
||||
"prefer-const": "error",
|
||||
"no-console": "error"
|
||||
},
|
||||
},
|
||||
// File-pattern specific overrides
|
||||
{
|
||||
files: ["tests/**/*"],
|
||||
rules: {
|
||||
"no-unused-expressions": "off",
|
||||
"no-console": "off"
|
||||
}
|
||||
},
|
||||
];
|
11223
package-lock.json
generated
11223
package-lock.json
generated
File diff suppressed because it is too large
Load diff
96
package.json
96
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "cyberchef",
|
||||
"version": "10.18.6",
|
||||
"version": "10.19.4",
|
||||
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
|
||||
"author": "n1474335 <n1474335@gmail.com>",
|
||||
"homepage": "https://gchq.github.io/CyberChef",
|
||||
|
@ -39,29 +39,33 @@
|
|||
"node >= 16"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.23.9",
|
||||
"@babel/eslint-parser": "^7.23.10",
|
||||
"@babel/plugin-syntax-import-assertions": "^7.23.3",
|
||||
"@babel/plugin-transform-runtime": "^7.23.9",
|
||||
"@babel/preset-env": "^7.23.9",
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@codemirror/commands": "^6.3.3",
|
||||
"@codemirror/language": "^6.10.1",
|
||||
"@codemirror/search": "^6.5.5",
|
||||
"@codemirror/state": "^6.4.0",
|
||||
"@codemirror/view": "^6.23.1",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"@babel/core": "^7.24.7",
|
||||
"@babel/eslint-parser": "^7.24.7",
|
||||
"@babel/plugin-syntax-import-assertions": "^7.24.7",
|
||||
"@babel/plugin-transform-runtime": "^7.24.7",
|
||||
"@babel/preset-env": "^7.24.7",
|
||||
"@babel/runtime": "^7.24.7",
|
||||
"@codemirror/commands": "^6.6.0",
|
||||
"@codemirror/language": "^6.10.2",
|
||||
"@codemirror/search": "^6.5.6",
|
||||
"@codemirror/state": "^6.4.1",
|
||||
"@codemirror/view": "^6.28.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"babel-loader": "^9.1.3",
|
||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||
"babel-plugin-transform-builtin-extend": "1.1.2",
|
||||
"base64-loader": "^1.0.0",
|
||||
"chromedriver": "^123.0.4",
|
||||
"chromedriver": "^130.0.0",
|
||||
"cli-progress": "^3.12.0",
|
||||
"colors": "^1.4.0",
|
||||
"compression-webpack-plugin": "^11.1.0",
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
"core-js": "^3.35.1",
|
||||
"css-loader": "6.10.0",
|
||||
"eslint": "^8.56.0",
|
||||
"core-js": "^3.37.1",
|
||||
"cspell": "^8.17.3",
|
||||
"css-loader": "7.1.2",
|
||||
"eslint": "^9.4.0",
|
||||
"eslint-plugin-jsdoc": "^48.2.9",
|
||||
"globals": "^15.4.0",
|
||||
"grunt": "^1.6.1",
|
||||
"grunt-chmod": "~1.1.1",
|
||||
"grunt-concurrent": "^3.0.0",
|
||||
|
@ -69,25 +73,25 @@
|
|||
"grunt-contrib-connect": "^4.0.0",
|
||||
"grunt-contrib-copy": "~1.0.0",
|
||||
"grunt-contrib-watch": "^1.1.0",
|
||||
"grunt-eslint": "^24.3.0",
|
||||
"grunt-eslint": "^25.0.0",
|
||||
"grunt-exec": "~3.0.0",
|
||||
"grunt-webpack": "^6.0.0",
|
||||
"grunt-zip": "^1.0.0",
|
||||
"html-webpack-plugin": "^5.6.0",
|
||||
"imports-loader": "^5.0.0",
|
||||
"mini-css-extract-plugin": "2.8.0",
|
||||
"modify-source-webpack-plugin": "^3.0.0",
|
||||
"nightwatch": "^3.4.0",
|
||||
"postcss": "^8.4.33",
|
||||
"mini-css-extract-plugin": "2.9.0",
|
||||
"modify-source-webpack-plugin": "^4.1.0",
|
||||
"nightwatch": "^3.6.3",
|
||||
"postcss": "^8.4.38",
|
||||
"postcss-css-variables": "^0.19.0",
|
||||
"postcss-import": "^16.0.0",
|
||||
"postcss-loader": "^8.1.0",
|
||||
"postcss-import": "^16.1.0",
|
||||
"postcss-loader": "^8.1.1",
|
||||
"prompt": "^1.3.0",
|
||||
"sitemap": "^7.1.1",
|
||||
"terser": "^5.27.0",
|
||||
"webpack": "^5.90.1",
|
||||
"webpack-bundle-analyzer": "^4.10.1",
|
||||
"webpack-dev-server": "4.15.1",
|
||||
"sitemap": "^8.0.0",
|
||||
"terser": "^5.31.1",
|
||||
"webpack": "^5.91.0",
|
||||
"webpack-bundle-analyzer": "^4.10.2",
|
||||
"webpack-dev-server": "5.0.4",
|
||||
"webpack-node-externals": "^3.0.0",
|
||||
"worker-loader": "^3.0.8"
|
||||
},
|
||||
|
@ -96,7 +100,7 @@
|
|||
"@babel/polyfill": "^7.12.1",
|
||||
"@blu3r4y/lzma": "^2.3.3",
|
||||
"@wavesenterprise/crypto-gost-js": "^2.1.0-RC1",
|
||||
"@xmldom/xmldom": "^0.8.0",
|
||||
"@xmldom/xmldom": "^0.8.10",
|
||||
"argon2-browser": "^1.18.0",
|
||||
"arrive": "^2.4.1",
|
||||
"avsc": "^5.7.7",
|
||||
|
@ -116,26 +120,27 @@
|
|||
"crypto-browserify": "^3.12.0",
|
||||
"crypto-js": "^4.2.0",
|
||||
"ctph.js": "0.0.5",
|
||||
"d3": "7.8.5",
|
||||
"d3": "7.9.0",
|
||||
"d3-hexbin": "^0.2.2",
|
||||
"diff": "^5.1.0",
|
||||
"diff": "^5.2.0",
|
||||
"dompurify": "^3.2.5",
|
||||
"es6-promisify": "^7.0.0",
|
||||
"escodegen": "^2.1.0",
|
||||
"esprima": "^4.0.1",
|
||||
"exif-parser": "^0.1.12",
|
||||
"fernet": "^0.3.2",
|
||||
"fernet": "^0.4.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"flat": "^6.0.1",
|
||||
"geodesy": "1.1.3",
|
||||
"highlight.js": "^11.9.0",
|
||||
"ieee754": "^1.1.13",
|
||||
"jimp": "^0.16.13",
|
||||
"ieee754": "^1.2.1",
|
||||
"jimp": "^0.22.12",
|
||||
"jq-web": "^0.5.1",
|
||||
"jquery": "3.7.1",
|
||||
"js-crc": "^0.2.0",
|
||||
"js-sha3": "^0.9.3",
|
||||
"jsesc": "^3.0.2",
|
||||
"json5": "^2.2.3",
|
||||
"jsonpath-plus": "^8.0.0",
|
||||
"jsonpath-plus": "^9.0.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"jsqr": "^1.4.0",
|
||||
"jsrsasign": "^11.1.0",
|
||||
|
@ -147,9 +152,9 @@
|
|||
"loglevel-message-prefix": "^3.0.0",
|
||||
"lz-string": "^1.5.0",
|
||||
"lz4js": "^0.2.0",
|
||||
"markdown-it": "^14.0.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.5.44",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"ngeohash": "^0.6.3",
|
||||
"node-forge": "^1.3.1",
|
||||
"node-md6": "^0.1.0",
|
||||
|
@ -157,13 +162,13 @@
|
|||
"notepack.io": "^3.0.1",
|
||||
"ntlm": "^0.1.3",
|
||||
"nwmatcher": "^1.4.4",
|
||||
"otp": "0.1.3",
|
||||
"otpauth": "9.3.6",
|
||||
"path": "^0.12.7",
|
||||
"popper.js": "^1.16.1",
|
||||
"process": "^0.11.10",
|
||||
"protobufjs": "^7.2.6",
|
||||
"protobufjs": "^7.3.1",
|
||||
"qr-image": "^3.2.0",
|
||||
"reflect-metadata": "^0.2.1",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rison": "^0.1.1",
|
||||
"scryptsy": "^2.1.0",
|
||||
"snackbarjs": "^1.1.0",
|
||||
|
@ -171,8 +176,8 @@
|
|||
"split.js": "^1.6.5",
|
||||
"ssdeep.js": "0.0.3",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"tesseract.js": "5.0.4",
|
||||
"ua-parser-js": "^1.0.37",
|
||||
"tesseract.js": "5.1.0",
|
||||
"ua-parser-js": "^1.0.38",
|
||||
"unorm": "^1.6.0",
|
||||
"utf8": "^3.0.0",
|
||||
"vkbeautify": "^0.99.3",
|
||||
|
@ -190,7 +195,8 @@
|
|||
"testui": "npx grunt testui",
|
||||
"testuidev": "npx nightwatch --env=dev",
|
||||
"lint": "npx grunt lint",
|
||||
"postinstall": "npx grunt exec:fixCryptoApiImports && npx grunt exec:fixSnackbarMarkup",
|
||||
"lint:grammar": "cspell ./src",
|
||||
"postinstall": "npx grunt exec:fixCryptoApiImports && npx grunt exec:fixSnackbarMarkup && npx grunt exec:fixJimpModule",
|
||||
"newop": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newOperation.mjs",
|
||||
"minor": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newMinorVersion.mjs",
|
||||
"getheapsize": "node -e 'console.log(`node heap limit = ${require(\"v8\").getHeapStatistics().heap_size_limit / (1024 * 1024)} Mb`)'",
|
||||
|
|
|
@ -72,9 +72,14 @@
|
|||
"Avro to JSON",
|
||||
"CBOR Encode",
|
||||
"CBOR Decode",
|
||||
"YAML to JSON",
|
||||
"JSON to YAML",
|
||||
"Caret/M-decode",
|
||||
"Rison Encode",
|
||||
"Rison Decode"
|
||||
"Rison Decode",
|
||||
"To Modhex",
|
||||
"From Modhex",
|
||||
"MIME Decoding"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -164,6 +169,7 @@
|
|||
"name": "Public Key",
|
||||
"ops": [
|
||||
"Parse X.509 certificate",
|
||||
"Parse X.509 CRL",
|
||||
"Parse ASN.1 hex string",
|
||||
"PEM to Hex",
|
||||
"Hex to PEM",
|
||||
|
@ -189,7 +195,9 @@
|
|||
"Parse SSH Host Key",
|
||||
"Parse CSR",
|
||||
"Public Key from Certificate",
|
||||
"Public Key from Private Key"
|
||||
"Public Key from Private Key",
|
||||
"SM2 Encrypt",
|
||||
"SM2 Decrypt"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -233,9 +241,14 @@
|
|||
"Parse User Agent",
|
||||
"Parse IP range",
|
||||
"Parse IPv6 address",
|
||||
"IPv6 Transition Addresses",
|
||||
"Parse IPv4 header",
|
||||
"Strip IPv4 header",
|
||||
"Parse TCP",
|
||||
"Strip TCP header",
|
||||
"Parse TLS record",
|
||||
"Parse UDP",
|
||||
"Strip UDP header",
|
||||
"Parse SSH Host Key",
|
||||
"Parse URI",
|
||||
"URL Encode",
|
||||
|
@ -268,7 +281,8 @@
|
|||
"Unicode Text Format",
|
||||
"Remove Diacritics",
|
||||
"Unescape Unicode Characters",
|
||||
"Convert to NATO alphabet"
|
||||
"Convert to NATO alphabet",
|
||||
"Convert Leet Speak"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -320,7 +334,9 @@
|
|||
"Unescape string",
|
||||
"Pseudo-Random Number Generator",
|
||||
"Sleep",
|
||||
"File Tree"
|
||||
"File Tree",
|
||||
"Take nth bytes",
|
||||
"Drop nth bytes"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -429,9 +445,7 @@
|
|||
"Fletcher-64 Checksum",
|
||||
"Adler-32 Checksum",
|
||||
"Luhn Checksum",
|
||||
"CRC-8 Checksum",
|
||||
"CRC-16 Checksum",
|
||||
"CRC-32 Checksum",
|
||||
"CRC Checksum",
|
||||
"TCP/IP Checksum"
|
||||
]
|
||||
},
|
||||
|
@ -453,8 +467,10 @@
|
|||
"CSS Minify",
|
||||
"XPath expression",
|
||||
"JPath expression",
|
||||
"Jq",
|
||||
"CSS selector",
|
||||
"PHP Deserialize",
|
||||
"PHP Serialize",
|
||||
"Microsoft Script Decoder",
|
||||
"Strip HTML tags",
|
||||
"Diff",
|
||||
|
|
|
@ -30,12 +30,12 @@ fs.readdirSync(path.join(dir, "../operations")).forEach(file => {
|
|||
|
||||
// Construct index file
|
||||
let code = `/**
|
||||
* THIS FILE IS AUTOMATICALLY GENERATED BY src/core/config/scripts/generateOpsIndex.mjs
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright ${new Date().getUTCFullYear()}
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
* THIS FILE IS AUTOMATICALLY GENERATED BY src/core/config/scripts/generateOpsIndex.mjs
|
||||
*
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright ${new Date().getUTCFullYear()}
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
`;
|
||||
|
||||
opObjs.forEach(obj => {
|
||||
|
|
23
src/core/lib/Base32.mjs
Normal file
23
src/core/lib/Base32.mjs
Normal file
|
@ -0,0 +1,23 @@
|
|||
// import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* Base32 resources.
|
||||
*
|
||||
* @author Peter C-S [petercs@purelymail.com]
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base32 alphabets.
|
||||
*/
|
||||
export const ALPHABET_OPTIONS = [
|
||||
{
|
||||
name: "Standard", // https://www.rfc-editor.org/rfc/rfc4648#section-6
|
||||
value: "A-Z2-7=",
|
||||
},
|
||||
{
|
||||
name: "Hex Extended", // https://www.rfc-editor.org/rfc/rfc4648#section-7
|
||||
value: "0-9A-V=",
|
||||
},
|
||||
];
|
||||
|
|
@ -62,3 +62,9 @@ export const URL_REGEX = new RegExp(protocol + hostname + "(?:" + port + ")?(?:"
|
|||
* Domain name regular expression
|
||||
*/
|
||||
export const DOMAIN_REGEX = /\b((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b/ig;
|
||||
|
||||
|
||||
/**
|
||||
* DMARC Domain name regular expression
|
||||
*/
|
||||
export const DMARC_DOMAIN_REGEX = /\b((?=[a-z0-9_-]{1,63}\.)(xn--)?[a-z0-9_]+(-[a-z0-9_]+)*\.)+[a-z]{2,63}\b/ig;
|
||||
|
|
|
@ -44,7 +44,7 @@ export function toJA4(bytes) {
|
|||
the TLS version is the value of the Protocol Version. Handshake version (located at the top of the packet)
|
||||
should be ignored.
|
||||
*/
|
||||
let version = tlsr.version.value;
|
||||
let version = tlsr.handshake.value.helloVersion.value;
|
||||
for (const ext of tlsr.handshake.value.extensions.value) {
|
||||
if (ext.type.value === "supported_versions") {
|
||||
version = parseHighestSupportedVersion(ext.value.data);
|
||||
|
@ -189,7 +189,7 @@ export function toJA4S(bytes) {
|
|||
the TLS version is the value of the Protocol Version. Handshake version (located at the top of the packet)
|
||||
should be ignored.
|
||||
*/
|
||||
let version = tlsr.version.value;
|
||||
let version = tlsr.handshake.value.helloVersion.value;
|
||||
for (const ext of tlsr.handshake.value.extensions.value) {
|
||||
if (ext.type.value === "supported_versions") {
|
||||
version = parseHighestSupportedVersion(ext.value.data);
|
||||
|
|
165
src/core/lib/Modhex.mjs
Normal file
165
src/core/lib/Modhex.mjs
Normal file
|
@ -0,0 +1,165 @@
|
|||
/**
|
||||
* @author linuxgemini [ilteris@asenkron.com.tr]
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Utils from "../Utils.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import { fromHex, toHex } from "./Hex.mjs";
|
||||
|
||||
/**
|
||||
* Modhex alphabet.
|
||||
*/
|
||||
const MODHEX_ALPHABET = "cbdefghijklnrtuv";
|
||||
|
||||
|
||||
/**
|
||||
* Modhex alphabet map.
|
||||
*/
|
||||
const MODHEX_ALPHABET_MAP = MODHEX_ALPHABET.split("");
|
||||
|
||||
|
||||
/**
|
||||
* Hex alphabet to substitute Modhex.
|
||||
*/
|
||||
const HEX_ALPHABET = "0123456789abcdef";
|
||||
|
||||
|
||||
/**
|
||||
* Hex alphabet map to substitute Modhex.
|
||||
*/
|
||||
const HEX_ALPHABET_MAP = HEX_ALPHABET.split("");
|
||||
|
||||
|
||||
/**
|
||||
* Convert a byte array into a modhex string.
|
||||
*
|
||||
* @param {byteArray|Uint8Array|ArrayBuffer} data
|
||||
* @param {string} [delim=" "]
|
||||
* @param {number} [padding=2]
|
||||
* @returns {string}
|
||||
*
|
||||
* @example
|
||||
* // returns "cl bf bu"
|
||||
* toModhex([10,20,30]);
|
||||
*
|
||||
* // returns "cl:bf:bu"
|
||||
* toModhex([10,20,30], ":");
|
||||
*/
|
||||
export function toModhex(data, delim=" ", padding=2, extraDelim="", lineSize=0) {
|
||||
if (!data) return "";
|
||||
if (data instanceof ArrayBuffer) data = new Uint8Array(data);
|
||||
|
||||
const regularHexString = toHex(data, "", padding, "", 0);
|
||||
|
||||
let modhexString = "";
|
||||
for (const letter of regularHexString.split("")) {
|
||||
modhexString += MODHEX_ALPHABET_MAP[HEX_ALPHABET_MAP.indexOf(letter)];
|
||||
}
|
||||
|
||||
let output = "";
|
||||
const groupingRegexp = new RegExp(`.{1,${padding}}`, "g");
|
||||
const groupedModhex = modhexString.match(groupingRegexp);
|
||||
|
||||
for (let i = 0; i < groupedModhex.length; i++) {
|
||||
const group = groupedModhex[i];
|
||||
output += group + delim;
|
||||
|
||||
if (extraDelim) {
|
||||
output += extraDelim;
|
||||
}
|
||||
// Add LF after each lineSize amount of bytes but not at the end
|
||||
if ((i !== groupedModhex.length - 1) && ((i + 1) % lineSize === 0)) {
|
||||
output += "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the extraDelim at the end (if there is one)
|
||||
// and remove the delim at the end, but if it's prepended there's nothing to remove
|
||||
const rTruncLen = extraDelim.length + delim.length;
|
||||
if (rTruncLen) {
|
||||
// If rTruncLen === 0 then output.slice(0,0) will be returned, which is nothing
|
||||
return output.slice(0, -rTruncLen);
|
||||
} else {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert a byte array into a modhex string as efficiently as possible with no options.
|
||||
*
|
||||
* @param {byteArray|Uint8Array|ArrayBuffer} data
|
||||
* @returns {string}
|
||||
*
|
||||
* @example
|
||||
* // returns "clbfbu"
|
||||
* toModhexFast([10,20,30]);
|
||||
*/
|
||||
export function toModhexFast(data) {
|
||||
if (!data) return "";
|
||||
if (data instanceof ArrayBuffer) data = new Uint8Array(data);
|
||||
|
||||
const output = [];
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
output.push(MODHEX_ALPHABET_MAP[(data[i] >> 4) & 0xf]);
|
||||
output.push(MODHEX_ALPHABET_MAP[data[i] & 0xf]);
|
||||
}
|
||||
return output.join("");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert a modhex string into a byte array.
|
||||
*
|
||||
* @param {string} data
|
||||
* @param {string} [delim]
|
||||
* @param {number} [byteLen=2]
|
||||
* @returns {byteArray}
|
||||
*
|
||||
* @example
|
||||
* // returns [10,20,30]
|
||||
* fromModhex("cl bf bu");
|
||||
*
|
||||
* // returns [10,20,30]
|
||||
* fromModhex("cl:bf:bu", "Colon");
|
||||
*/
|
||||
export function fromModhex(data, delim="Auto", byteLen=2) {
|
||||
if (byteLen < 1 || Math.round(byteLen) !== byteLen)
|
||||
throw new OperationError("Byte length must be a positive integer");
|
||||
|
||||
// The `.replace(/\s/g, "")` an interesting workaround: Hex "multiline" tests aren't actually
|
||||
// multiline. Tests for Modhex fixes that, thus exposing the issue.
|
||||
data = data.toLowerCase().replace(/\s/g, "");
|
||||
|
||||
if (delim !== "None") {
|
||||
const delimRegex = delim === "Auto" ? /[^cbdefghijklnrtuv]/gi : Utils.regexRep(delim);
|
||||
data = data.split(delimRegex);
|
||||
} else {
|
||||
data = [data];
|
||||
}
|
||||
|
||||
let regularHexString = "";
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
for (const letter of data[i].split("")) {
|
||||
regularHexString += HEX_ALPHABET_MAP[MODHEX_ALPHABET_MAP.indexOf(letter)];
|
||||
}
|
||||
}
|
||||
|
||||
const output = fromHex(regularHexString, "None", byteLen);
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* To Modhex delimiters.
|
||||
*/
|
||||
export const TO_MODHEX_DELIM_OPTIONS = ["Space", "Percent", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "None"];
|
||||
|
||||
|
||||
/**
|
||||
* From Modhex delimiters.
|
||||
*/
|
||||
export const FROM_MODHEX_DELIM_OPTIONS = ["Auto"].concat(TO_MODHEX_DELIM_OPTIONS);
|
|
@ -26,6 +26,9 @@ export function objToTable(obj, nested=false) {
|
|||
</tr>`;
|
||||
|
||||
for (const key in obj) {
|
||||
if (typeof obj[key] === "function")
|
||||
continue;
|
||||
|
||||
html += `<tr><td style='word-wrap: break-word'>${key}</td>`;
|
||||
if (typeof obj[key] === "object")
|
||||
html += `<td style='padding: 0'>${objToTable(obj[key], true)}</td>`;
|
||||
|
|
|
@ -10,7 +10,7 @@ import OperationError from "../errors/OperationError.mjs";
|
|||
import jsQR from "jsqr";
|
||||
import qr from "qr-image";
|
||||
import Utils from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Parses a QR code image from an image
|
||||
|
@ -22,7 +22,7 @@ import jimp from "jimp";
|
|||
export async function parseQrCode(input, normalise) {
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error opening image. (${err})`);
|
||||
}
|
||||
|
@ -33,8 +33,8 @@ export async function parseQrCode(input, normalise) {
|
|||
image.background(0xFFFFFFFF);
|
||||
image.normalize();
|
||||
image.greyscale();
|
||||
image = await image.getBufferAsync(jimp.MIME_JPEG);
|
||||
image = await jimp.read(image);
|
||||
image = await image.getBufferAsync(Jimp.MIME_JPEG);
|
||||
image = await Jimp.read(image);
|
||||
}
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error normalising image. (${err})`);
|
||||
|
|
258
src/core/lib/SM2.mjs
Normal file
258
src/core/lib/SM2.mjs
Normal file
|
@ -0,0 +1,258 @@
|
|||
/**
|
||||
* Utilities and operations utilized for SM2 encryption and decryption
|
||||
* @author flakjacket95 [dflack95@gmail.com]
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import { fromHex } from "../lib/Hex.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import Sm3 from "crypto-api/src/hasher/sm3.mjs";
|
||||
import {toHex} from "crypto-api/src/encoder/hex.mjs";
|
||||
import r from "jsrsasign";
|
||||
|
||||
/**
|
||||
* SM2 Class for encryption and decryption operations
|
||||
*/
|
||||
export class SM2 {
|
||||
/**
|
||||
* Constructor for SM2 class; sets up with the curve and the output format as specified in user args
|
||||
*
|
||||
* @param {*} curve
|
||||
* @param {*} format
|
||||
*/
|
||||
constructor(curve, format) {
|
||||
this.ecParams = null;
|
||||
this.rng = new r.SecureRandom();
|
||||
/*
|
||||
For any additional curve definitions utilized by SM2, add another block like the below for that curve, then add the curve name to the Curve selection dropdown
|
||||
*/
|
||||
r.crypto.ECParameterDB.regist(
|
||||
"sm2p256v1", // name / p = 2**256 - 2**224 - 2**96 + 2**64 - 1
|
||||
256,
|
||||
"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", // p
|
||||
"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", // a
|
||||
"28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", // b
|
||||
"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", // n
|
||||
"1", // h
|
||||
"32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", // gx
|
||||
"BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", // gy
|
||||
[]
|
||||
); // alias
|
||||
this.ecParams = r.crypto.ECParameterDB.getByName(curve);
|
||||
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the public key coordinates for the SM2 class
|
||||
*
|
||||
* @param {string} publicKeyX
|
||||
* @param {string} publicKeyY
|
||||
*/
|
||||
setPublicKey(publicKeyX, publicKeyY) {
|
||||
/*
|
||||
* TODO: This needs some additional length validation; and checking for errors in the decoding process
|
||||
* TODO: Can probably support other public key encoding methods here as well in the future
|
||||
*/
|
||||
this.publicKey = this.ecParams.curve.decodePointHex("04" + publicKeyX + publicKeyY);
|
||||
|
||||
if (this.publicKey.isInfinity()) {
|
||||
throw new OperationError("Invalid Public Key");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the private key value for the SM2 class
|
||||
*
|
||||
* @param {string} privateKey
|
||||
*/
|
||||
setPrivateKey(privateKeyHex) {
|
||||
this.privateKey = new r.BigInteger(privateKeyHex, 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main encryption function; takes user input, processes encryption and returns the result in hex (with the components arranged as configured by the user args)
|
||||
*
|
||||
* @param {*} input
|
||||
* @returns {string}
|
||||
*/
|
||||
encrypt(input) {
|
||||
const G = this.ecParams.G;
|
||||
|
||||
/*
|
||||
* Compute a new, random public key along the same elliptic curve to form the starting point for our encryption process (record the resulting X and Y as hex to provide as part of the operation output)
|
||||
* k: Randomly generated BigInteger
|
||||
* c1: Result of dotting our curve generator point `G` with the value of `k`
|
||||
*/
|
||||
const k = this.generatePublicKey();
|
||||
const c1 = G.multiply(k);
|
||||
const [hexC1X, hexC1Y] = this.getPointAsHex(c1);
|
||||
|
||||
/*
|
||||
* Compute p2 (secret) using the public key, and the chosen k value above
|
||||
*/
|
||||
const p2 = this.publicKey.multiply(k);
|
||||
|
||||
/*
|
||||
* Compute the C3 SM3 hash before we transform the array
|
||||
*/
|
||||
const c3 = this.c3(p2, input);
|
||||
|
||||
/*
|
||||
* Genreate a proper length encryption key, XOR iteratively, and convert newly encrypted data to hex
|
||||
*/
|
||||
const key = this.kdf(p2, input.byteLength);
|
||||
for (let i = 0; i < input.byteLength; i++) {
|
||||
input[i] ^= Utils.ord(key[i]);
|
||||
}
|
||||
const c2 = Buffer.from(input).toString("hex");
|
||||
|
||||
/*
|
||||
* Check user input specs; order the output components as selected
|
||||
*/
|
||||
if (this.format === "C1C3C2") {
|
||||
return hexC1X + hexC1Y + c3 + c2;
|
||||
} else {
|
||||
return hexC1X + hexC1Y + c2 + c3;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Function to decrypt an SM2 encrypted message
|
||||
*
|
||||
* @param {*} input
|
||||
*/
|
||||
decrypt(input) {
|
||||
const c1X = input.slice(0, 64);
|
||||
const c1Y = input.slice(64, 128);
|
||||
|
||||
let c3 = "";
|
||||
let c2 = "";
|
||||
|
||||
if (this.format === "C1C3C2") {
|
||||
c3 = input.slice(128, 192);
|
||||
c2 = input.slice(192);
|
||||
} else {
|
||||
c2 = input.slice(128, -64);
|
||||
c3 = input.slice(-64);
|
||||
}
|
||||
c2 = Uint8Array.from(fromHex(c2));
|
||||
const c1 = this.ecParams.curve.decodePointHex("04" + c1X + c1Y);
|
||||
|
||||
/*
|
||||
* Compute the p2 (secret) value by taking the C1 point provided in the encrypted package, and multiplying by the private k value
|
||||
*/
|
||||
const p2 = c1.multiply(this.privateKey);
|
||||
|
||||
/*
|
||||
* Similar to encryption; compute sufficient length key material and XOR the input data to recover the original message
|
||||
*/
|
||||
const key = this.kdf(p2, c2.byteLength);
|
||||
|
||||
for (let i = 0; i < c2.byteLength; i++) {
|
||||
c2[i] ^= Utils.ord(key[i]);
|
||||
}
|
||||
|
||||
const check = this.c3(p2, c2);
|
||||
if (check === c3) {
|
||||
return c2.buffer;
|
||||
} else {
|
||||
throw new OperationError("Decryption Error -- Computed Hashes Do Not Match");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates a large random number
|
||||
*
|
||||
* @param {*} limit
|
||||
* @returns
|
||||
*/
|
||||
getBigRandom(limit) {
|
||||
return new r.BigInteger(limit.bitLength(), this.rng)
|
||||
.mod(limit.subtract(r.BigInteger.ONE))
|
||||
.add(r.BigInteger.ONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for generating a large random K number; utilized for generating our initial C1 point
|
||||
* TODO: Do we need to do any sort of validation on the resulting k values?
|
||||
*
|
||||
* @returns {BigInteger}
|
||||
*/
|
||||
generatePublicKey() {
|
||||
const n = this.ecParams.n;
|
||||
const k = this.getBigRandom(n);
|
||||
return k;
|
||||
}
|
||||
|
||||
/**
|
||||
* SM2 Key Derivation Function (KDF); Takes P2 point, and generates a key material stream large enough to encrypt all of the input data
|
||||
*
|
||||
* @param {*} p2
|
||||
* @param {*} len
|
||||
* @returns {string}
|
||||
*/
|
||||
kdf(p2, len) {
|
||||
const [hX, hY] = this.getPointAsHex(p2);
|
||||
|
||||
const total = Math.ceil(len / 32) + 1;
|
||||
let cnt = 1;
|
||||
|
||||
let keyMaterial = "";
|
||||
|
||||
while (cnt < total) {
|
||||
const num = Utils.intToByteArray(cnt, 4, "big");
|
||||
const overall = fromHex(hX).concat(fromHex(hY)).concat(num);
|
||||
keyMaterial += this.sm3(overall);
|
||||
cnt++;
|
||||
}
|
||||
return keyMaterial;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the C3 component of our final encrypted payload; which is the SM3 hash of the P2 point and the original, unencrypted input data
|
||||
*
|
||||
* @param {*} p2
|
||||
* @param {*} input
|
||||
* @returns {string}
|
||||
*/
|
||||
c3(p2, input) {
|
||||
const [hX, hY] = this.getPointAsHex(p2);
|
||||
|
||||
const overall = fromHex(hX).concat(Array.from(input)).concat(fromHex(hY));
|
||||
|
||||
return toHex(this.sm3(overall));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* SM3 setup helper function; takes input data as an array, processes the hash and returns the result
|
||||
*
|
||||
* @param {*} data
|
||||
* @returns {string}
|
||||
*/
|
||||
sm3(data) {
|
||||
const hashData = Utils.arrayBufferToStr(Uint8Array.from(data).buffer, false);
|
||||
const hasher = new Sm3();
|
||||
hasher.update(hashData);
|
||||
return hasher.finalize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function, returns an elliptic curve points X and Y values as hex;
|
||||
*
|
||||
* @param {EcPointFp} point
|
||||
* @returns {[]}
|
||||
*/
|
||||
getPointAsHex(point) {
|
||||
const biX = point.getX().toBigInteger();
|
||||
const biY = point.getY().toBigInteger();
|
||||
|
||||
const charlen = this.ecParams.keycharlen;
|
||||
const hX = ("0000000000" + biX.toString(16)).slice(- charlen);
|
||||
const hY = ("0000000000" + biY.toString(16)).slice(- charlen);
|
||||
return [hX, hY];
|
||||
}
|
||||
}
|
|
@ -112,7 +112,7 @@ class AESDecrypt extends Operation {
|
|||
run(input, args) {
|
||||
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
||||
iv = Utils.convertToByteString(args[1].string, args[1].option),
|
||||
mode = args[2].substring(0, 3),
|
||||
mode = args[2].split("/")[0],
|
||||
noPadding = args[2].endsWith("NoPadding"),
|
||||
inputType = args[3],
|
||||
outputType = args[4],
|
||||
|
|
|
@ -66,6 +66,14 @@ class AESEncrypt extends Operation {
|
|||
{
|
||||
name: "ECB",
|
||||
off: [5]
|
||||
},
|
||||
{
|
||||
name: "CBC/NoPadding",
|
||||
off: [5]
|
||||
},
|
||||
{
|
||||
name: "ECB/NoPadding",
|
||||
off: [5]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -98,7 +106,8 @@ class AESEncrypt extends Operation {
|
|||
run(input, args) {
|
||||
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
||||
iv = Utils.convertToByteString(args[1].string, args[1].option),
|
||||
mode = args[2],
|
||||
mode = args[2].split("/")[0],
|
||||
noPadding = args[2].endsWith("NoPadding"),
|
||||
inputType = args[3],
|
||||
outputType = args[4],
|
||||
aad = Utils.convertToByteString(args[5].string, args[5].option);
|
||||
|
@ -114,11 +123,20 @@ The following algorithms will be used based on the size of the key:
|
|||
|
||||
input = Utils.convertToByteString(input, inputType);
|
||||
|
||||
// Handle NoPadding modes
|
||||
if (noPadding && input.length % 16 !== 0) {
|
||||
throw new OperationError("Input length must be a multiple of 16 bytes for NoPadding modes.");
|
||||
}
|
||||
const cipher = forge.cipher.createCipher("AES-" + mode, key);
|
||||
cipher.start({
|
||||
iv: iv,
|
||||
additionalData: mode === "GCM" ? aad : undefined
|
||||
});
|
||||
if (noPadding) {
|
||||
cipher.mode.pad = function(output, options) {
|
||||
return true;
|
||||
};
|
||||
}
|
||||
cipher.update(forge.util.createBuffer(input));
|
||||
cipher.finish();
|
||||
|
||||
|
|
|
@ -22,7 +22,13 @@ class AddLineNumbers extends Operation {
|
|||
this.description = "Adds line numbers to the output.";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
this.args = [
|
||||
{
|
||||
"name": "Offset",
|
||||
"type": "number",
|
||||
"value": 0
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -33,10 +39,11 @@ class AddLineNumbers extends Operation {
|
|||
run(input, args) {
|
||||
const lines = input.split("\n"),
|
||||
width = lines.length.toString().length;
|
||||
const offset = args[0] ? parseInt(args[0], 10) : 0;
|
||||
let output = "";
|
||||
|
||||
for (let n = 0; n < lines.length; n++) {
|
||||
output += (n+1).toString().padStart(width, " ") + " " + lines[n] + "\n";
|
||||
output += (n+1+offset).toString().padStart(width, " ") + " " + lines[n] + "\n";
|
||||
}
|
||||
return output.slice(0, output.length-1);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
|||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Add Text To Image operation
|
||||
|
@ -127,7 +127,7 @@ class AddTextToImage extends Operation {
|
|||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
|
@ -163,7 +163,7 @@ class AddTextToImage extends Operation {
|
|||
const font = fontsMap[fontFace];
|
||||
|
||||
// LoadFont needs an absolute url, so append the font name to self.docURL
|
||||
const jimpFont = await jimp.loadFont(self.docURL + "/" + font.default);
|
||||
const jimpFont = await Jimp.loadFont(self.docURL + "/" + font.default);
|
||||
|
||||
jimpFont.pages.forEach(function(page) {
|
||||
if (page.bitmap) {
|
||||
|
@ -190,7 +190,7 @@ class AddTextToImage extends Operation {
|
|||
});
|
||||
|
||||
// Create a temporary image to hold the rendered text
|
||||
const textImage = new jimp(jimp.measureText(jimpFont, text), jimp.measureTextHeight(jimpFont, text));
|
||||
const textImage = new Jimp(Jimp.measureText(jimpFont, text), Jimp.measureTextHeight(jimpFont, text));
|
||||
textImage.print(jimpFont, 0, 0, text);
|
||||
|
||||
// Scale the rendered text image to the correct size
|
||||
|
@ -198,9 +198,9 @@ class AddTextToImage extends Operation {
|
|||
if (size !== 1) {
|
||||
// Use bicubic for decreasing size
|
||||
if (size > 1) {
|
||||
textImage.scale(scaleFactor, jimp.RESIZE_BICUBIC);
|
||||
textImage.scale(scaleFactor, Jimp.RESIZE_BICUBIC);
|
||||
} else {
|
||||
textImage.scale(scaleFactor, jimp.RESIZE_BILINEAR);
|
||||
textImage.scale(scaleFactor, Jimp.RESIZE_BILINEAR);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -234,9 +234,9 @@ class AddTextToImage extends Operation {
|
|||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
|
|
@ -76,8 +76,8 @@ class BlowfishDecrypt extends Operation {
|
|||
Blowfish's key length needs to be between 4 and 56 bytes (32-448 bits).`);
|
||||
}
|
||||
|
||||
if (iv.length !== 8) {
|
||||
throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes`);
|
||||
if (mode !== "ECB" && iv.length !== 8) {
|
||||
throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes.`);
|
||||
}
|
||||
|
||||
input = Utils.convertToByteString(input, inputType);
|
||||
|
|
|
@ -72,12 +72,12 @@ class BlowfishEncrypt extends Operation {
|
|||
|
||||
if (key.length < 4 || key.length > 56) {
|
||||
throw new OperationError(`Invalid key length: ${key.length} bytes
|
||||
|
||||
|
||||
Blowfish's key length needs to be between 4 and 56 bytes (32-448 bits).`);
|
||||
}
|
||||
|
||||
if (iv.length !== 8) {
|
||||
throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes`);
|
||||
if (mode !== "ECB" && iv.length !== 8) {
|
||||
throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes.`);
|
||||
}
|
||||
|
||||
input = Utils.convertToByteString(input, inputType);
|
||||
|
|
|
@ -10,7 +10,7 @@ import { isWorkerEnvironment } from "../Utils.mjs";
|
|||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { gaussianBlur } from "../lib/ImageManipulation.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Blur Image operation
|
||||
|
@ -59,7 +59,7 @@ class BlurImage extends Operation {
|
|||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
|
@ -79,9 +79,9 @@ class BlurImage extends Operation {
|
|||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import JSCRC from "js-crc";
|
||||
|
||||
/**
|
||||
* CRC-16 Checksum operation
|
||||
*/
|
||||
class CRC16Checksum extends Operation {
|
||||
|
||||
/**
|
||||
* CRC16Checksum constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "CRC-16 Checksum";
|
||||
this.module = "Crypto";
|
||||
this.description = "A cyclic redundancy check (CRC) is an error-detecting code commonly used in digital networks and storage devices to detect accidental changes to raw data.<br><br>The CRC was invented by W. Wesley Peterson in 1961.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Cyclic_redundancy_check";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
return JSCRC.crc16(input);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CRC16Checksum;
|
|
@ -1,41 +0,0 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import JSCRC from "js-crc";
|
||||
|
||||
/**
|
||||
* CRC-32 Checksum operation
|
||||
*/
|
||||
class CRC32Checksum extends Operation {
|
||||
|
||||
/**
|
||||
* CRC32Checksum constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "CRC-32 Checksum";
|
||||
this.module = "Crypto";
|
||||
this.description = "A cyclic redundancy check (CRC) is an error-detecting code commonly used in digital networks and storage devices to detect accidental changes to raw data.<br><br>The CRC was invented by W. Wesley Peterson in 1961; the 32-bit CRC function of Ethernet and many other standards is the work of several researchers and was published in 1975.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Cyclic_redundancy_check";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
return JSCRC.crc32(input);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CRC32Checksum;
|
|
@ -1,157 +0,0 @@
|
|||
/**
|
||||
* @author mshwed [m@ttshwed.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
import { toHexFast } from "../lib/Hex.mjs";
|
||||
|
||||
/**
|
||||
* CRC-8 Checksum operation
|
||||
*/
|
||||
class CRC8Checksum extends Operation {
|
||||
|
||||
/**
|
||||
* CRC8Checksum constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "CRC-8 Checksum";
|
||||
this.module = "Crypto";
|
||||
this.description = "A cyclic redundancy check (CRC) is an error-detecting code commonly used in digital networks and storage devices to detect accidental changes to raw data.<br><br>The CRC was invented by W. Wesley Peterson in 1961.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Cyclic_redundancy_check";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Algorithm",
|
||||
"type": "option",
|
||||
"value": [
|
||||
"CRC-8",
|
||||
"CRC-8/CDMA2000",
|
||||
"CRC-8/DARC",
|
||||
"CRC-8/DVB-S2",
|
||||
"CRC-8/EBU",
|
||||
"CRC-8/I-CODE",
|
||||
"CRC-8/ITU",
|
||||
"CRC-8/MAXIM",
|
||||
"CRC-8/ROHC",
|
||||
"CRC-8/WCDMA"
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the pre-computed lookup table for byte division
|
||||
*
|
||||
* @param polynomial
|
||||
*/
|
||||
calculateCRC8LookupTable(polynomial) {
|
||||
const crc8Table = new Uint8Array(256);
|
||||
|
||||
let currentByte;
|
||||
for (let i = 0; i < 256; i++) {
|
||||
currentByte = i;
|
||||
for (let bit = 0; bit < 8; bit++) {
|
||||
if ((currentByte & 0x80) !== 0) {
|
||||
currentByte <<= 1;
|
||||
currentByte ^= polynomial;
|
||||
} else {
|
||||
currentByte <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
crc8Table[i] = currentByte;
|
||||
}
|
||||
|
||||
return crc8Table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the CRC-8 Checksum from an input
|
||||
*
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {number} polynomial
|
||||
* @param {number} initializationValue
|
||||
* @param {boolean} inputReflection
|
||||
* @param {boolean} outputReflection
|
||||
* @param {number} xorOut
|
||||
*/
|
||||
calculateCRC8(input, polynomial, initializationValue, inputReflection, outputReflection, xorOut) {
|
||||
const crcSize = 8;
|
||||
const crcTable = this.calculateCRC8LookupTable(polynomial);
|
||||
|
||||
let crc = initializationValue !== 0 ? initializationValue : 0;
|
||||
let currentByte, position;
|
||||
|
||||
input = new Uint8Array(input);
|
||||
for (const inputByte of input) {
|
||||
currentByte = inputReflection ? this.reverseBits(inputByte, crcSize) : inputByte;
|
||||
|
||||
position = (currentByte ^ crc) & 255;
|
||||
crc = crcTable[position];
|
||||
}
|
||||
|
||||
crc = outputReflection ? this.reverseBits(crc, crcSize) : crc;
|
||||
|
||||
if (xorOut !== 0) crc = crc ^ xorOut;
|
||||
|
||||
return toHexFast(new Uint8Array([crc]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the bits for a given input byte.
|
||||
*
|
||||
* @param {number} input
|
||||
*/
|
||||
reverseBits(input, hashSize) {
|
||||
let reversedByte = 0;
|
||||
for (let i = hashSize - 1; i >= 0; i--) {
|
||||
reversedByte |= ((input & 1) << i);
|
||||
input >>= 1;
|
||||
}
|
||||
|
||||
return reversedByte;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const algorithm = args[0];
|
||||
|
||||
switch (algorithm) {
|
||||
case "CRC-8":
|
||||
return this.calculateCRC8(input, 0x7, 0x0, false, false, 0x0);
|
||||
case "CRC-8/CDMA2000":
|
||||
return this.calculateCRC8(input, 0x9B, 0xFF, false, false, 0x0);
|
||||
case "CRC-8/DARC":
|
||||
return this.calculateCRC8(input, 0x39, 0x0, true, true, 0x0);
|
||||
case "CRC-8/DVB-S2":
|
||||
return this.calculateCRC8(input, 0xD5, 0x0, false, false, 0x0);
|
||||
case "CRC-8/EBU":
|
||||
return this.calculateCRC8(input, 0x1D, 0xFF, true, true, 0x0);
|
||||
case "CRC-8/I-CODE":
|
||||
return this.calculateCRC8(input, 0x1D, 0xFD, false, false, 0x0);
|
||||
case "CRC-8/ITU":
|
||||
return this.calculateCRC8(input, 0x7, 0x0, false, false, 0x55);
|
||||
case "CRC-8/MAXIM":
|
||||
return this.calculateCRC8(input, 0x31, 0x0, true, true, 0x0);
|
||||
case "CRC-8/ROHC":
|
||||
return this.calculateCRC8(input, 0x7, 0xFF, true, true, 0x0);
|
||||
case "CRC-8/WCDMA":
|
||||
return this.calculateCRC8(input, 0x9B, 0x0, true, true, 0x0);
|
||||
default:
|
||||
throw new OperationError("Unknown checksum algorithm");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default CRC8Checksum;
|
1110
src/core/operations/CRCChecksum.mjs
Normal file
1110
src/core/operations/CRCChecksum.mjs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
|||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Contain Image operation
|
||||
|
@ -91,20 +91,20 @@ class ContainImage extends Operation {
|
|||
const [width, height, hAlign, vAlign, alg, opaqueBg] = args;
|
||||
|
||||
const resizeMap = {
|
||||
"Nearest Neighbour": jimp.RESIZE_NEAREST_NEIGHBOR,
|
||||
"Bilinear": jimp.RESIZE_BILINEAR,
|
||||
"Bicubic": jimp.RESIZE_BICUBIC,
|
||||
"Hermite": jimp.RESIZE_HERMITE,
|
||||
"Bezier": jimp.RESIZE_BEZIER
|
||||
"Nearest Neighbour": Jimp.RESIZE_NEAREST_NEIGHBOR,
|
||||
"Bilinear": Jimp.RESIZE_BILINEAR,
|
||||
"Bicubic": Jimp.RESIZE_BICUBIC,
|
||||
"Hermite": Jimp.RESIZE_HERMITE,
|
||||
"Bezier": Jimp.RESIZE_BEZIER
|
||||
};
|
||||
|
||||
const alignMap = {
|
||||
"Left": jimp.HORIZONTAL_ALIGN_LEFT,
|
||||
"Center": jimp.HORIZONTAL_ALIGN_CENTER,
|
||||
"Right": jimp.HORIZONTAL_ALIGN_RIGHT,
|
||||
"Top": jimp.VERTICAL_ALIGN_TOP,
|
||||
"Middle": jimp.VERTICAL_ALIGN_MIDDLE,
|
||||
"Bottom": jimp.VERTICAL_ALIGN_BOTTOM
|
||||
"Left": Jimp.HORIZONTAL_ALIGN_LEFT,
|
||||
"Center": Jimp.HORIZONTAL_ALIGN_CENTER,
|
||||
"Right": Jimp.HORIZONTAL_ALIGN_RIGHT,
|
||||
"Top": Jimp.VERTICAL_ALIGN_TOP,
|
||||
"Middle": Jimp.VERTICAL_ALIGN_MIDDLE,
|
||||
"Bottom": Jimp.VERTICAL_ALIGN_BOTTOM
|
||||
};
|
||||
|
||||
if (!isImage(input)) {
|
||||
|
@ -113,7 +113,7 @@ class ContainImage extends Operation {
|
|||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
|
@ -123,16 +123,16 @@ class ContainImage extends Operation {
|
|||
image.contain(width, height, alignMap[hAlign] | alignMap[vAlign], resizeMap[alg]);
|
||||
|
||||
if (opaqueBg) {
|
||||
const newImage = await jimp.read(width, height, 0x000000FF);
|
||||
const newImage = await Jimp.read(width, height, 0x000000FF);
|
||||
newImage.blit(image, 0, 0);
|
||||
image = newImage;
|
||||
}
|
||||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
|
|
@ -8,7 +8,7 @@ import Operation from "../Operation.mjs";
|
|||
import OperationError from "../errors/OperationError.mjs";
|
||||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Convert Image Format operation
|
||||
|
@ -76,19 +76,19 @@ class ConvertImageFormat extends Operation {
|
|||
async run(input, args) {
|
||||
const [format, jpegQuality, pngFilterType, pngDeflateLevel] = args;
|
||||
const formatMap = {
|
||||
"JPEG": jimp.MIME_JPEG,
|
||||
"PNG": jimp.MIME_PNG,
|
||||
"BMP": jimp.MIME_BMP,
|
||||
"TIFF": jimp.MIME_TIFF
|
||||
"JPEG": Jimp.MIME_JPEG,
|
||||
"PNG": Jimp.MIME_PNG,
|
||||
"BMP": Jimp.MIME_BMP,
|
||||
"TIFF": Jimp.MIME_TIFF
|
||||
};
|
||||
|
||||
const pngFilterMap = {
|
||||
"Auto": jimp.PNG_FILTER_AUTO,
|
||||
"None": jimp.PNG_FILTER_NONE,
|
||||
"Sub": jimp.PNG_FILTER_SUB,
|
||||
"Up": jimp.PNG_FILTER_UP,
|
||||
"Average": jimp.PNG_FILTER_AVERAGE,
|
||||
"Paeth": jimp.PNG_FILTER_PATH
|
||||
"Auto": Jimp.PNG_FILTER_AUTO,
|
||||
"None": Jimp.PNG_FILTER_NONE,
|
||||
"Sub": Jimp.PNG_FILTER_SUB,
|
||||
"Up": Jimp.PNG_FILTER_UP,
|
||||
"Average": Jimp.PNG_FILTER_AVERAGE,
|
||||
"Paeth": Jimp.PNG_FILTER_PATH
|
||||
};
|
||||
|
||||
const mime = formatMap[format];
|
||||
|
@ -98,7 +98,7 @@ class ConvertImageFormat extends Operation {
|
|||
}
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error opening image file. (${err})`);
|
||||
}
|
||||
|
|
116
src/core/operations/ConvertLeetSpeak.mjs
Normal file
116
src/core/operations/ConvertLeetSpeak.mjs
Normal file
|
@ -0,0 +1,116 @@
|
|||
/**
|
||||
* @author bartblaze []
|
||||
* @copyright Crown Copyright 2025
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
|
||||
/**
|
||||
* Convert Leet Speak operation
|
||||
*/
|
||||
class ConvertLeetSpeak extends Operation {
|
||||
/**
|
||||
* ConvertLeetSpeak constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Convert Leet Speak";
|
||||
this.module = "Default";
|
||||
this.description = "Converts to and from Leet Speak.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Leet";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Direction",
|
||||
type: "option",
|
||||
value: ["To Leet Speak", "From Leet Speak"],
|
||||
defaultIndex: 0
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const direction = args[0];
|
||||
|
||||
if (direction === "To Leet Speak") {
|
||||
return input.replace(/[a-z]/gi, char => {
|
||||
const leetChar = toLeetMap[char.toLowerCase()] || char;
|
||||
return char === char.toUpperCase() ? leetChar.toUpperCase() : leetChar;
|
||||
});
|
||||
} else if (direction === "From Leet Speak") {
|
||||
return input.replace(/[48cd3f6h1jklmn0pqr57uvwxyz]/gi, char => {
|
||||
const normalChar = fromLeetMap[char] || char;
|
||||
return normalChar;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const toLeetMap = {
|
||||
"a": "4",
|
||||
"b": "b",
|
||||
"c": "c",
|
||||
"d": "d",
|
||||
"e": "3",
|
||||
"f": "f",
|
||||
"g": "g",
|
||||
"h": "h",
|
||||
"i": "1",
|
||||
"j": "j",
|
||||
"k": "k",
|
||||
"l": "l",
|
||||
"m": "m",
|
||||
"n": "n",
|
||||
"o": "0",
|
||||
"p": "p",
|
||||
"q": "q",
|
||||
"r": "r",
|
||||
"s": "5",
|
||||
"t": "7",
|
||||
"u": "u",
|
||||
"v": "v",
|
||||
"w": "w",
|
||||
"x": "x",
|
||||
"y": "y",
|
||||
"z": "z"
|
||||
};
|
||||
|
||||
const fromLeetMap = {
|
||||
"4": "a",
|
||||
"b": "b",
|
||||
"c": "c",
|
||||
"d": "d",
|
||||
"3": "e",
|
||||
"f": "f",
|
||||
"g": "g",
|
||||
"h": "h",
|
||||
"1": "i",
|
||||
"j": "j",
|
||||
"k": "k",
|
||||
"l": "l",
|
||||
"m": "m",
|
||||
"n": "n",
|
||||
"0": "o",
|
||||
"p": "p",
|
||||
"q": "q",
|
||||
"r": "r",
|
||||
"5": "s",
|
||||
"7": "t",
|
||||
"u": "u",
|
||||
"v": "v",
|
||||
"w": "w",
|
||||
"x": "x",
|
||||
"y": "y",
|
||||
"z": "z"
|
||||
};
|
||||
|
||||
export default ConvertLeetSpeak;
|
||||
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
|||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Cover Image operation
|
||||
|
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
|||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Crop Image operation
|
||||
|
@ -99,7 +99,7 @@ class CropImage extends Operation {
|
|||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
|
@ -119,9 +119,9 @@ class CropImage extends Operation {
|
|||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
|
|
@ -22,7 +22,7 @@ class DESDecrypt extends Operation {
|
|||
|
||||
this.name = "DES Decrypt";
|
||||
this.module = "Ciphers";
|
||||
this.description = "DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.<br><br><b>Key:</b> DES uses a key length of 8 bytes (64 bits).<br>Triple DES uses a key length of 24 bytes (192 bits).<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used as a default.";
|
||||
this.description = "DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.<br><br><b>Key:</b> DES uses a key length of 8 bytes (64 bits).<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used as a default.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Data_Encryption_Standard";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
|
@ -72,8 +72,7 @@ class DESDecrypt extends Operation {
|
|||
if (key.length !== 8) {
|
||||
throw new OperationError(`Invalid key length: ${key.length} bytes
|
||||
|
||||
DES uses a key length of 8 bytes (64 bits).
|
||||
Triple DES uses a key length of 24 bytes (192 bits).`);
|
||||
DES uses a key length of 8 bytes (64 bits).`);
|
||||
}
|
||||
if (iv.length !== 8 && mode !== "ECB") {
|
||||
throw new OperationError(`Invalid IV length: ${iv.length} bytes
|
||||
|
|
|
@ -22,7 +22,7 @@ class DESEncrypt extends Operation {
|
|||
|
||||
this.name = "DES Encrypt";
|
||||
this.module = "Ciphers";
|
||||
this.description = "DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.<br><br><b>Key:</b> DES uses a key length of 8 bytes (64 bits).<br>Triple DES uses a key length of 24 bytes (192 bits).<br><br>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.";
|
||||
this.description = "DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.<br><br><b>Key:</b> DES uses a key length of 8 bytes (64 bits).<br><br>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Data_Encryption_Standard";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
|
@ -70,8 +70,7 @@ class DESEncrypt extends Operation {
|
|||
if (key.length !== 8) {
|
||||
throw new OperationError(`Invalid key length: ${key.length} bytes
|
||||
|
||||
DES uses a key length of 8 bytes (64 bits).
|
||||
Triple DES uses a key length of 24 bytes (192 bits).`);
|
||||
DES uses a key length of 8 bytes (64 bits).`);
|
||||
}
|
||||
if (iv.length !== 8 && mode !== "ECB") {
|
||||
throw new OperationError(`Invalid IV length: ${iv.length} bytes
|
||||
|
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
|||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Image Dither operation
|
||||
|
@ -44,7 +44,7 @@ class DitherImage extends Operation {
|
|||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
|
@ -55,9 +55,9 @@ class DitherImage extends Operation {
|
|||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
|
79
src/core/operations/DropNthBytes.mjs
Normal file
79
src/core/operations/DropNthBytes.mjs
Normal file
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* @author Oshawk [oshawk@protonmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* Drop nth bytes operation
|
||||
*/
|
||||
class DropNthBytes extends Operation {
|
||||
|
||||
/**
|
||||
* DropNthBytes constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Drop nth bytes";
|
||||
this.module = "Default";
|
||||
this.description = "Drops every nth byte starting with a given byte.";
|
||||
this.infoURL = "";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "byteArray";
|
||||
this.args = [
|
||||
{
|
||||
name: "Drop every",
|
||||
type: "number",
|
||||
value: 4
|
||||
},
|
||||
{
|
||||
name: "Starting at",
|
||||
type: "number",
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
name: "Apply to each line",
|
||||
type: "boolean",
|
||||
value: false
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
run(input, args) {
|
||||
const n = args[0];
|
||||
const start = args[1];
|
||||
const eachLine = args[2];
|
||||
|
||||
if (parseInt(n, 10) !== n || n <= 0) {
|
||||
throw new OperationError("'Drop every' must be a positive integer.");
|
||||
}
|
||||
if (parseInt(start, 10) !== start || start < 0) {
|
||||
throw new OperationError("'Starting at' must be a positive or zero integer.");
|
||||
}
|
||||
|
||||
let offset = 0;
|
||||
const output = [];
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
if (eachLine && input[i] === 0x0a) {
|
||||
output.push(0x0a);
|
||||
offset = i + 1;
|
||||
} else if (i - offset < start || (i - (start + offset)) % n !== 0) {
|
||||
output.push(input[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default DropNthBytes;
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { search, DOMAIN_REGEX } from "../lib/Extract.mjs";
|
||||
import { search, DOMAIN_REGEX, DMARC_DOMAIN_REGEX } from "../lib/Extract.mjs";
|
||||
import { caseInsensitiveSort } from "../lib/Sort.mjs";
|
||||
|
||||
/**
|
||||
|
@ -39,6 +39,11 @@ class ExtractDomains extends Operation {
|
|||
name: "Unique",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "Underscore (DMARC, DKIM, etc)",
|
||||
type: "boolean",
|
||||
value: false
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -49,11 +54,11 @@ class ExtractDomains extends Operation {
|
|||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [displayTotal, sort, unique] = args;
|
||||
const [displayTotal, sort, unique, dmarc] = args;
|
||||
|
||||
const results = search(
|
||||
input,
|
||||
DOMAIN_REGEX,
|
||||
dmarc ? DMARC_DOMAIN_REGEX : DOMAIN_REGEX,
|
||||
null,
|
||||
sort ? caseInsensitiveSort : null,
|
||||
unique
|
||||
|
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
|||
import Utils from "../Utils.mjs";
|
||||
import { fromBinary } from "../lib/Binary.mjs";
|
||||
import { isImage } from "../lib/FileType.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Extract LSB operation
|
||||
|
@ -73,7 +73,7 @@ class ExtractLSB extends Operation {
|
|||
const bit = 7 - args.pop(),
|
||||
pixelOrder = args.pop(),
|
||||
colours = args.filter(option => option !== "").map(option => COLOUR_OPTIONS.indexOf(option)),
|
||||
parsedImage = await jimp.read(input),
|
||||
parsedImage = await Jimp.read(input),
|
||||
width = parsedImage.bitmap.width,
|
||||
height = parsedImage.bitmap.height,
|
||||
rgba = parsedImage.bitmap.data;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import { isImage } from "../lib/FileType.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
import {RGBA_DELIM_OPTIONS} from "../lib/Delim.mjs";
|
||||
|
||||
|
@ -52,7 +52,7 @@ class ExtractRGBA extends Operation {
|
|||
|
||||
const delimiter = args[0],
|
||||
includeAlpha = args[1],
|
||||
parsedImage = await jimp.read(input);
|
||||
parsedImage = await Jimp.read(input);
|
||||
|
||||
let bitmap = parsedImage.bitmap.data;
|
||||
bitmap = includeAlpha ? bitmap : bitmap.filter((val, idx) => idx % 4 !== 3);
|
||||
|
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
|||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Flip Image operation
|
||||
|
@ -51,7 +51,7 @@ class FlipImage extends Operation {
|
|||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
|
@ -69,9 +69,9 @@ class FlipImage extends Operation {
|
|||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import {ALPHABET_OPTIONS} from "../lib/Base32.mjs";
|
||||
|
||||
|
||||
/**
|
||||
* From Base32 operation
|
||||
|
@ -27,8 +29,8 @@ class FromBase32 extends Operation {
|
|||
this.args = [
|
||||
{
|
||||
name: "Alphabet",
|
||||
type: "binaryString",
|
||||
value: "A-Z2-7="
|
||||
type: "editableOption",
|
||||
value: ALPHABET_OPTIONS
|
||||
},
|
||||
{
|
||||
name: "Remove non-alphabet chars",
|
||||
|
@ -41,6 +43,11 @@ class FromBase32 extends Operation {
|
|||
pattern: "^(?:[A-Z2-7]{8})+(?:[A-Z2-7]{2}={6}|[A-Z2-7]{4}={4}|[A-Z2-7]{5}={3}|[A-Z2-7]{7}={1})?$",
|
||||
flags: "",
|
||||
args: ["A-Z2-7=", false]
|
||||
},
|
||||
{
|
||||
pattern: "^(?:[0-9A-V]{8})+(?:[0-9A-V]{2}={6}|[0-9A-V]{4}={4}|[0-9A-V]{5}={3}|[0-9A-V]{7}={1})?$",
|
||||
flags: "",
|
||||
args: ["0-9A-V=", false]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -96,3 +103,4 @@ class FromBase32 extends Operation {
|
|||
}
|
||||
|
||||
export default FromBase32;
|
||||
|
||||
|
|
84
src/core/operations/FromModhex.mjs
Normal file
84
src/core/operations/FromModhex.mjs
Normal file
|
@ -0,0 +1,84 @@
|
|||
/**
|
||||
* @author linuxgemini [ilteris@asenkron.com.tr]
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { FROM_MODHEX_DELIM_OPTIONS, fromModhex } from "../lib/Modhex.mjs";
|
||||
|
||||
/**
|
||||
* From Modhex operation
|
||||
*/
|
||||
class FromModhex extends Operation {
|
||||
|
||||
/**
|
||||
* FromModhex constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "From Modhex";
|
||||
this.module = "Default";
|
||||
this.description = "Converts a modhex byte string back into its raw value.";
|
||||
this.infoURL = "https://en.wikipedia.org/wiki/YubiKey#ModHex";
|
||||
this.inputType = "string";
|
||||
this.outputType = "byteArray";
|
||||
this.args = [
|
||||
{
|
||||
name: "Delimiter",
|
||||
type: "option",
|
||||
value: FROM_MODHEX_DELIM_OPTIONS
|
||||
}
|
||||
];
|
||||
this.checks = [
|
||||
{
|
||||
pattern: "^(?:[cbdefghijklnrtuv]{2})+$",
|
||||
flags: "i",
|
||||
args: ["None"]
|
||||
},
|
||||
{
|
||||
pattern: "^[cbdefghijklnrtuv]{2}(?: [cbdefghijklnrtuv]{2})*$",
|
||||
flags: "i",
|
||||
args: ["Space"]
|
||||
},
|
||||
{
|
||||
pattern: "^[cbdefghijklnrtuv]{2}(?:,[cbdefghijklnrtuv]{2})*$",
|
||||
flags: "i",
|
||||
args: ["Comma"]
|
||||
},
|
||||
{
|
||||
pattern: "^[cbdefghijklnrtuv]{2}(?:;[cbdefghijklnrtuv]{2})*$",
|
||||
flags: "i",
|
||||
args: ["Semi-colon"]
|
||||
},
|
||||
{
|
||||
pattern: "^[cbdefghijklnrtuv]{2}(?::[cbdefghijklnrtuv]{2})*$",
|
||||
flags: "i",
|
||||
args: ["Colon"]
|
||||
},
|
||||
{
|
||||
pattern: "^[cbdefghijklnrtuv]{2}(?:\\n[cbdefghijklnrtuv]{2})*$",
|
||||
flags: "i",
|
||||
args: ["Line feed"]
|
||||
},
|
||||
{
|
||||
pattern: "^[cbdefghijklnrtuv]{2}(?:\\r\\n[cbdefghijklnrtuv]{2})*$",
|
||||
flags: "i",
|
||||
args: ["CRLF"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
run(input, args) {
|
||||
const delim = args[0] || "Auto";
|
||||
return fromModhex(input, delim, 2);
|
||||
}
|
||||
}
|
||||
|
||||
export default FromModhex;
|
|
@ -56,7 +56,7 @@ class GOSTDecrypt extends Operation {
|
|||
value: [
|
||||
{
|
||||
name: "GOST 28147 (1989)",
|
||||
on: [6]
|
||||
on: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Magma, 2015)",
|
||||
|
|
|
@ -56,7 +56,7 @@ class GOSTEncrypt extends Operation {
|
|||
value: [
|
||||
{
|
||||
name: "GOST 28147 (1989)",
|
||||
on: [6]
|
||||
on: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Magma, 2015)",
|
||||
|
|
|
@ -56,7 +56,7 @@ class GOSTKeyUnwrap extends Operation {
|
|||
value: [
|
||||
{
|
||||
name: "GOST 28147 (1989)",
|
||||
on: [6]
|
||||
on: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Magma, 2015)",
|
||||
|
|
|
@ -56,7 +56,7 @@ class GOSTKeyWrap extends Operation {
|
|||
value: [
|
||||
{
|
||||
name: "GOST 28147 (1989)",
|
||||
on: [6]
|
||||
on: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Magma, 2015)",
|
||||
|
|
|
@ -56,7 +56,7 @@ class GOSTSign extends Operation {
|
|||
value: [
|
||||
{
|
||||
name: "GOST 28147 (1989)",
|
||||
on: [6]
|
||||
on: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Magma, 2015)",
|
||||
|
|
|
@ -57,7 +57,7 @@ class GOSTVerify extends Operation {
|
|||
value: [
|
||||
{
|
||||
name: "GOST 28147 (1989)",
|
||||
on: [6]
|
||||
on: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Magma, 2015)",
|
||||
|
|
|
@ -27,9 +27,7 @@ import Fletcher16Checksum from "./Fletcher16Checksum.mjs";
|
|||
import Fletcher32Checksum from "./Fletcher32Checksum.mjs";
|
||||
import Fletcher64Checksum from "./Fletcher64Checksum.mjs";
|
||||
import Adler32Checksum from "./Adler32Checksum.mjs";
|
||||
import CRC8Checksum from "./CRC8Checksum.mjs";
|
||||
import CRC16Checksum from "./CRC16Checksum.mjs";
|
||||
import CRC32Checksum from "./CRC32Checksum.mjs";
|
||||
import CRCChecksum from "./CRCChecksum.mjs";
|
||||
import BLAKE2b from "./BLAKE2b.mjs";
|
||||
import BLAKE2s from "./BLAKE2s.mjs";
|
||||
import Streebog from "./Streebog.mjs";
|
||||
|
@ -120,9 +118,9 @@ class GenerateAllHashes extends Operation {
|
|||
{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 CRC8Checksum), inputType: "arrayBuffer", params: ["CRC-8"]},
|
||||
{name: "CRC-16", algo: (new CRC16Checksum), inputType: "arrayBuffer", params: []},
|
||||
{name: "CRC-32", algo: (new CRC32Checksum), inputType: "arrayBuffer", 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"]}
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -5,16 +5,14 @@
|
|||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import otp from "otp";
|
||||
import ToBase32 from "./ToBase32.mjs";
|
||||
import * as OTPAuth from "otpauth";
|
||||
|
||||
/**
|
||||
* Generate HOTP operation
|
||||
*/
|
||||
class GenerateHOTP extends Operation {
|
||||
|
||||
/**
|
||||
* GenerateHOTP constructor
|
||||
*
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
@ -31,11 +29,6 @@ class GenerateHOTP extends Operation {
|
|||
"type": "string",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"name": "Key size",
|
||||
"type": "number",
|
||||
"value": 32
|
||||
},
|
||||
{
|
||||
"name": "Code length",
|
||||
"type": "number",
|
||||
|
@ -50,21 +43,26 @@ class GenerateHOTP extends Operation {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*
|
||||
*/
|
||||
run(input, args) {
|
||||
const otpObj = otp({
|
||||
name: args[0],
|
||||
keySize: args[1],
|
||||
codeLength: args[2],
|
||||
secret: (new ToBase32).run(input, []).split("=")[0],
|
||||
});
|
||||
const counter = args[3];
|
||||
return `URI: ${otpObj.hotpURL}\n\nPassword: ${otpObj.hotp(counter)}`;
|
||||
}
|
||||
const secretStr = new TextDecoder("utf-8").decode(input).trim();
|
||||
const secret = secretStr ? secretStr.toUpperCase().replace(/\s+/g, "") : "";
|
||||
|
||||
const hotp = new OTPAuth.HOTP({
|
||||
issuer: "",
|
||||
label: args[0],
|
||||
algorithm: "SHA1",
|
||||
digits: args[1],
|
||||
counter: args[2],
|
||||
secret: OTPAuth.Secret.fromBase32(secret)
|
||||
});
|
||||
|
||||
const uri = hotp.toString();
|
||||
const code = hotp.generate();
|
||||
|
||||
return `URI: ${uri}\n\nPassword: ${code}`;
|
||||
}
|
||||
}
|
||||
|
||||
export default GenerateHOTP;
|
||||
|
|
|
@ -10,7 +10,7 @@ import Utils from "../Utils.mjs";
|
|||
import {isImage} from "../lib/FileType.mjs";
|
||||
import {toBase64} from "../lib/Base64.mjs";
|
||||
import {isWorkerEnvironment} from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Generate Image operation
|
||||
|
@ -81,7 +81,7 @@ class GenerateImage extends Operation {
|
|||
}
|
||||
|
||||
const height = Math.ceil(input.length / bytesPerPixel / width);
|
||||
const image = await new jimp(width, height, (err, image) => {});
|
||||
const image = await new Jimp(width, height, (err, image) => {});
|
||||
|
||||
if (isWorkerEnvironment())
|
||||
self.sendStatusMessage("Generating image from data...");
|
||||
|
@ -95,7 +95,7 @@ class GenerateImage extends Operation {
|
|||
const y = Math.floor(index / width);
|
||||
|
||||
const value = curByte[k] === "0" ? 0xFF : 0x00;
|
||||
const pixel = jimp.rgbaToInt(value, value, value, 0xFF);
|
||||
const pixel = Jimp.rgbaToInt(value, value, value, 0xFF);
|
||||
image.setPixelColor(pixel, x, y);
|
||||
}
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ class GenerateImage extends Operation {
|
|||
}
|
||||
|
||||
try {
|
||||
const pixel = jimp.rgbaToInt(red, green, blue, alpha);
|
||||
const pixel = Jimp.rgbaToInt(red, green, blue, alpha);
|
||||
image.setPixelColor(pixel, x, y);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error while generating image from pixel values. (${err})`);
|
||||
|
@ -151,11 +151,11 @@ class GenerateImage extends Operation {
|
|||
if (isWorkerEnvironment())
|
||||
self.sendStatusMessage("Scaling image...");
|
||||
|
||||
image.scaleToFit(width*scale, height*scale, jimp.RESIZE_NEAREST_NEIGHBOR);
|
||||
image.scaleToFit(width*scale, height*scale, Jimp.RESIZE_NEAREST_NEIGHBOR);
|
||||
}
|
||||
|
||||
try {
|
||||
const imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
const imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error generating image. (${err})`);
|
||||
|
|
|
@ -5,20 +5,17 @@
|
|||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import otp from "otp";
|
||||
import ToBase32 from "./ToBase32.mjs";
|
||||
import * as OTPAuth from "otpauth";
|
||||
|
||||
/**
|
||||
* Generate TOTP operation
|
||||
*/
|
||||
class GenerateTOTP extends Operation {
|
||||
|
||||
/**
|
||||
* GenerateTOTP constructor
|
||||
*
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Generate TOTP";
|
||||
this.module = "Default";
|
||||
this.description = "The Time-based One-Time Password algorithm (TOTP) is an algorithm that computes a one-time password from a shared secret key and the current time. It has been adopted as Internet Engineering Task Force standard RFC 6238, is the cornerstone of Initiative For Open Authentication (OAUTH), and is used in a number of two-factor authentication systems. A TOTP is an HOTP where the counter is the current time.<br><br>Enter the secret as the input or leave it blank for a random secret to be generated. T0 and T1 are in seconds.";
|
||||
|
@ -31,11 +28,6 @@ class GenerateTOTP extends Operation {
|
|||
"type": "string",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"name": "Key size",
|
||||
"type": "number",
|
||||
"value": 32
|
||||
},
|
||||
{
|
||||
"name": "Code length",
|
||||
"type": "number",
|
||||
|
@ -55,22 +47,27 @@ class GenerateTOTP extends Operation {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*
|
||||
*/
|
||||
run(input, args) {
|
||||
const otpObj = otp({
|
||||
name: args[0],
|
||||
keySize: args[1],
|
||||
codeLength: args[2],
|
||||
secret: (new ToBase32).run(input, []).split("=")[0],
|
||||
epoch: args[3],
|
||||
timeSlice: args[4]
|
||||
});
|
||||
return `URI: ${otpObj.totpURL}\n\nPassword: ${otpObj.totp()}`;
|
||||
}
|
||||
const secretStr = new TextDecoder("utf-8").decode(input).trim();
|
||||
const secret = secretStr ? secretStr.toUpperCase().replace(/\s+/g, "") : "";
|
||||
|
||||
const totp = new OTPAuth.TOTP({
|
||||
issuer: "",
|
||||
label: args[0],
|
||||
algorithm: "SHA1",
|
||||
digits: args[1],
|
||||
period: args[3],
|
||||
epoch: args[2] * 1000, // Convert seconds to milliseconds
|
||||
secret: OTPAuth.Secret.fromBase32(secret)
|
||||
});
|
||||
|
||||
const uri = totp.toString();
|
||||
const code = totp.generate();
|
||||
|
||||
return `URI: ${uri}\n\nPassword: ${code}`;
|
||||
}
|
||||
}
|
||||
|
||||
export default GenerateTOTP;
|
||||
|
|
209
src/core/operations/IPv6TransitionAddresses.mjs
Normal file
209
src/core/operations/IPv6TransitionAddresses.mjs
Normal file
|
@ -0,0 +1,209 @@
|
|||
/**
|
||||
* @author jb30795 [jb30795@proton.me]
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
|
||||
/**
|
||||
* IPv6 Transition Addresses operation
|
||||
*/
|
||||
class IPv6TransitionAddresses extends Operation {
|
||||
|
||||
/**
|
||||
* IPv6TransitionAddresses constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "IPv6 Transition Addresses";
|
||||
this.module = "Default";
|
||||
this.description = "Converts IPv4 addresses to their IPv6 Transition addresses. IPv6 Transition addresses can also be converted back into their original IPv4 address. MAC addresses can also be converted into the EUI-64 format, this can them be appended to your IPv6 /64 range to obtain a full /128 address.<br><br>Transition technologies enable translation between IPv4 and IPv6 addresses or tunneling to allow traffic to pass through the incompatible network, allowing the two standards to coexist.<br><br>Only /24 ranges and currently handled. Remove headers to easily copy out results.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/IPv6_transition_mechanism";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Ignore ranges",
|
||||
"type": "boolean",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "Remove headers",
|
||||
"type": "boolean",
|
||||
"value": false
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const XOR = {"0": "2", "1": "3", "2": "0", "3": "1", "4": "6", "5": "7", "6": "4", "7": "5", "8": "a", "9": "b", "a": "8", "b": "9", "c": "e", "d": "f", "e": "c", "f": "d"};
|
||||
|
||||
/**
|
||||
* Function to convert to hex
|
||||
*/
|
||||
function hexify(octet) {
|
||||
return Number(octet).toString(16).padStart(2, "0");
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to convert Hex to Int
|
||||
*/
|
||||
function intify(hex) {
|
||||
return parseInt(hex, 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function converts IPv4 to IPv6 Transtion address
|
||||
*/
|
||||
function ipTransition(input, range) {
|
||||
let output = "";
|
||||
const HEXIP = input.split(".");
|
||||
|
||||
/**
|
||||
* 6to4
|
||||
*/
|
||||
if (!args[1]) {
|
||||
output += "6to4: ";
|
||||
}
|
||||
output += "2002:" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]);
|
||||
if (range) {
|
||||
output += "00::/40\n";
|
||||
} else {
|
||||
output += hexify(HEXIP[3]) + "::/48\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapped
|
||||
*/
|
||||
if (!args[1]) {
|
||||
output += "IPv4 Mapped: ";
|
||||
}
|
||||
output += "::ffff:" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]);
|
||||
if (range) {
|
||||
output += "00/120\n";
|
||||
} else {
|
||||
output += hexify(HEXIP[3]) + "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Translated
|
||||
*/
|
||||
if (!args[1]) {
|
||||
output += "IPv4 Translated: ";
|
||||
}
|
||||
output += "::ffff:0:" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]);
|
||||
if (range) {
|
||||
output += "00/120\n";
|
||||
} else {
|
||||
output += hexify(HEXIP[3]) + "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Nat64
|
||||
*/
|
||||
if (!args[1]) {
|
||||
output += "Nat 64: ";
|
||||
}
|
||||
output += "64:ff9b::" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]);
|
||||
if (range) {
|
||||
output += "00/120\n";
|
||||
} else {
|
||||
output += hexify(HEXIP[3]) + "\n";
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert MAC to EUI-64
|
||||
*/
|
||||
function macTransition(input) {
|
||||
let output = "";
|
||||
const MACPARTS = input.split(":");
|
||||
if (!args[1]) {
|
||||
output += "EUI-64 Interface ID: ";
|
||||
}
|
||||
const MAC = MACPARTS[0] + MACPARTS[1] + ":" + MACPARTS[2] + "ff:fe" + MACPARTS[3] + ":" + MACPARTS[4] + MACPARTS[5];
|
||||
output += MAC.slice(0, 1) + XOR[MAC.slice(1, 2)] + MAC.slice(2);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert IPv6 address to its original IPv4 or MAC address
|
||||
*/
|
||||
function unTransition(input) {
|
||||
let output = "";
|
||||
let hextets = "";
|
||||
|
||||
/**
|
||||
* 6to4
|
||||
*/
|
||||
if (input.startsWith("2002:")) {
|
||||
if (!args[1]) {
|
||||
output += "IPv4: ";
|
||||
}
|
||||
output += String(intify(input.slice(5, 7))) + "." + String(intify(input.slice(7, 9)))+ "." + String(intify(input.slice(10, 12)))+ "." + String(intify(input.slice(12, 14))) + "\n";
|
||||
} else if (input.startsWith("::ffff:") || input.startsWith("0000:0000:0000:0000:0000:ffff:") || input.startsWith("::ffff:0000:") || input.startsWith("0000:0000:0000:0000:ffff:0000:") || input.startsWith("64:ff9b::") || input.startsWith("0064:ff9b:0000:0000:0000:0000:")) {
|
||||
/**
|
||||
* Mapped/Translated/Nat64
|
||||
*/
|
||||
hextets = /:([0-9a-z]{1,4}):[0-9a-z]{1,4}$/.exec(input)[1].padStart(4, "0") + /:([0-9a-z]{1,4})$/.exec(input)[1].padStart(4, "0");
|
||||
if (!args[1]) {
|
||||
output += "IPv4: ";
|
||||
}
|
||||
output += intify(hextets.slice(-8, -7) + hextets.slice(-7, -6)) + "." +intify(hextets.slice(-6, -5) + hextets.slice(-5, -4)) + "." +intify(hextets.slice(-4, -3) + hextets.slice(-3, -2)) + "." +intify(hextets.slice(-2, -1) + hextets.slice(-1,)) + "\n";
|
||||
} else if (input.slice(-12, -7).toUpperCase() === "FF:FE") {
|
||||
/**
|
||||
* EUI-64
|
||||
*/
|
||||
if (!args[1]) {
|
||||
output += "Mac Address: ";
|
||||
}
|
||||
const MAC = (input.slice(-19, -17) + ":" + input.slice(-17, -15) + ":" + input.slice(-14, -12) + ":" + input.slice(-7, -5) + ":" + input.slice(-4, -2) + ":" + input.slice(-2,)).toUpperCase();
|
||||
output += MAC.slice(0, 1) + XOR[MAC.slice(1, 2)] + MAC.slice(2) + "\n";
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Main
|
||||
*/
|
||||
let output = "";
|
||||
let inputs = input.split("\n");
|
||||
// Remove blank rows
|
||||
inputs = inputs.filter(Boolean);
|
||||
|
||||
for (let i = 0; i < inputs.length; i++) {
|
||||
// if ignore ranges is checked and input is a range, skip
|
||||
if ((args[0] && !inputs[i].includes("/")) || (!args[0])) {
|
||||
if (/^[0-9]{1,3}(?:\.[0-9]{1,3}){3}$/.test(inputs[i])) {
|
||||
output += ipTransition(inputs[i], false);
|
||||
} else if (/\/24$/.test(inputs[i])) {
|
||||
output += ipTransition(inputs[i], true);
|
||||
} else if (/^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$/.test(inputs[i])) {
|
||||
output += macTransition(inputs[i]);
|
||||
} else if (/^((?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(?::[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(?:ffff(?::0{1,4}){0,1}:){0,1}(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])|(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/.test(inputs[i])) {
|
||||
output += unTransition(inputs[i]);
|
||||
} else {
|
||||
output = "Enter compressed or expanded IPv6 address, IPv4 address or MAC Address.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default IPv6TransitionAddresses;
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
|||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Image Brightness / Contrast operation
|
||||
|
@ -60,7 +60,7 @@ class ImageBrightnessContrast extends Operation {
|
|||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
|
@ -78,9 +78,9 @@ class ImageBrightnessContrast extends Operation {
|
|||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
|||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Image Filter operation
|
||||
|
@ -54,7 +54,7 @@ class ImageFilter extends Operation {
|
|||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
|
@ -69,9 +69,9 @@ class ImageFilter extends Operation {
|
|||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
|||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Image Hue/Saturation/Lightness operation
|
||||
|
@ -68,7 +68,7 @@ class ImageHueSaturationLightness extends Operation {
|
|||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
|
@ -106,9 +106,9 @@ class ImageHueSaturationLightness extends Operation {
|
|||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
|||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Image Opacity operation
|
||||
|
@ -53,7 +53,7 @@ class ImageOpacity extends Operation {
|
|||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
|
@ -64,9 +64,9 @@ class ImageOpacity extends Operation {
|
|||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
|||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Invert Image operation
|
||||
|
@ -44,7 +44,7 @@ class InvertImage extends Operation {
|
|||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
|
@ -55,9 +55,9 @@ class InvertImage extends Operation {
|
|||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
|
|
@ -35,12 +35,6 @@ class JPathExpression extends Operation {
|
|||
name: "Result delimiter",
|
||||
type: "binaryShortString",
|
||||
value: "\\n"
|
||||
},
|
||||
{
|
||||
name: "Prevent eval",
|
||||
type: "boolean",
|
||||
value: true,
|
||||
description: "Evaluated expressions are disabled by default for security reasons"
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -51,7 +45,7 @@ class JPathExpression extends Operation {
|
|||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [query, delimiter, preventEval] = args;
|
||||
const [query, delimiter] = args;
|
||||
let results, jsonObj;
|
||||
|
||||
try {
|
||||
|
@ -63,8 +57,7 @@ class JPathExpression extends Operation {
|
|||
try {
|
||||
results = JSONPath({
|
||||
path: query,
|
||||
json: jsonObj,
|
||||
preventEval: preventEval
|
||||
json: jsonObj
|
||||
});
|
||||
} catch (err) {
|
||||
throw new OperationError(`Invalid JPath expression: ${err.message}`);
|
||||
|
|
46
src/core/operations/JSONtoYAML.mjs
Normal file
46
src/core/operations/JSONtoYAML.mjs
Normal file
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* @author ccarpo [ccarpo@gmx.net]
|
||||
* @copyright Crown Copyright 2021
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import YAML from "yaml";
|
||||
|
||||
/**
|
||||
* JSON to YAML operation
|
||||
*/
|
||||
class JSONtoYAML extends Operation {
|
||||
|
||||
/**
|
||||
* JSONtoYAML constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "JSON to YAML";
|
||||
this.module = "Default";
|
||||
this.description = "Format a JSON object into YAML";
|
||||
this.infoURL = "https://en.wikipedia.org/wiki/YAML";
|
||||
this.inputType = "JSON";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {JSON} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
try {
|
||||
return YAML.stringify(input);
|
||||
} catch (err) {
|
||||
throw new OperationError("Test");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default JSONtoYAML;
|
|
@ -36,6 +36,11 @@ class JWTSign extends Operation {
|
|||
name: "Signing algorithm",
|
||||
type: "option",
|
||||
value: JWT_ALGORITHMS
|
||||
},
|
||||
{
|
||||
name: "Header",
|
||||
type: "text",
|
||||
value: "{}"
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -46,11 +51,12 @@ class JWTSign extends Operation {
|
|||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [key, algorithm] = args;
|
||||
const [key, algorithm, header] = args;
|
||||
|
||||
try {
|
||||
return jwt.sign(input, key, {
|
||||
algorithm: algorithm === "None" ? "none" : algorithm
|
||||
algorithm: algorithm === "None" ? "none" : algorithm,
|
||||
header: JSON.parse(header || "{}")
|
||||
});
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error: Have you entered the key correctly? The key should be either the secret for HMAC algorithms or the PEM-encoded private key for RSA and ECDSA.
|
||||
|
|
|
@ -22,7 +22,7 @@ class JWTVerify extends Operation {
|
|||
|
||||
this.name = "JWT Verify";
|
||||
this.module = "Crypto";
|
||||
this.description = "Verifies that a JSON Web Token is valid and has been signed with the provided secret / private key.<br><br>The key should be either the secret for HMAC algorithms or the PEM-encoded private key for RSA and ECDSA.";
|
||||
this.description = "Verifies that a JSON Web Token is valid and has been signed with the provided secret / private key.<br><br>The key should be either the secret for HMAC algorithms or the PEM-encoded public key for RSA and ECDSA.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/JSON_Web_Token";
|
||||
this.inputType = "string";
|
||||
this.outputType = "JSON";
|
||||
|
|
57
src/core/operations/Jq.mjs
Normal file
57
src/core/operations/Jq.mjs
Normal file
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* @author zhzy0077 [zhzy0077@hotmail.com]
|
||||
* @copyright Crown Copyright 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import jq from "jq-web";
|
||||
|
||||
/**
|
||||
* jq operation
|
||||
*/
|
||||
class Jq extends Operation {
|
||||
|
||||
/**
|
||||
* Jq constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Jq";
|
||||
this.module = "Jq";
|
||||
this.description = "jq is a lightweight and flexible command-line JSON processor.";
|
||||
this.infoURL = "https://github.com/jqlang/jq";
|
||||
this.inputType = "JSON";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Query",
|
||||
type: "string",
|
||||
value: ""
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {JSON} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [query] = args;
|
||||
let result;
|
||||
|
||||
try {
|
||||
result = jq.json(input, query);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Invalid jq expression: ${err.message}`);
|
||||
}
|
||||
|
||||
return JSON.stringify(result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Jq;
|
|
@ -1,5 +1,6 @@
|
|||
/**
|
||||
* @author n1073645 [n1073645@gmail.com]
|
||||
* @author k3ach [k3ach@proton.me]
|
||||
* @copyright Crown Copyright 2020
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
@ -20,39 +21,46 @@ class LuhnChecksum extends Operation {
|
|||
|
||||
this.name = "Luhn Checksum";
|
||||
this.module = "Default";
|
||||
this.description = "The Luhn algorithm, also known as the modulus 10 or mod 10 algorithm, is a simple checksum formula used to validate a variety of identification numbers, such as credit card numbers, IMEI numbers and Canadian Social Insurance Numbers.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Luhn_algorithm";
|
||||
this.description = "The Luhn mod N algorithm using the english alphabet. The Luhn mod N algorithm is an extension to the Luhn algorithm (also known as mod 10 algorithm) that allows it to work with sequences of values in any even-numbered base. This can be useful when a check digit is required to validate an identification string composed of letters, a combination of letters and digits or any arbitrary set of N characters where N is divisible by 2.";
|
||||
this.infoURL = "https://en.wikipedia.org/wiki/Luhn_mod_N_algorithm";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
this.args = [
|
||||
{
|
||||
"name": "Radix",
|
||||
"type": "number",
|
||||
"value": 10
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the Luhn Checksum from the input.
|
||||
* Generates the Luhn checksum from the input.
|
||||
*
|
||||
* @param {string} inputStr
|
||||
* @returns {number}
|
||||
*/
|
||||
checksum(inputStr) {
|
||||
checksum(inputStr, radix = 10) {
|
||||
let even = false;
|
||||
return inputStr.split("").reverse().reduce((acc, elem) => {
|
||||
// Convert element to integer.
|
||||
let temp = parseInt(elem, 10);
|
||||
// Convert element to an integer based on the provided radix.
|
||||
let temp = parseInt(elem, radix);
|
||||
|
||||
// If element is not an integer.
|
||||
if (isNaN(temp))
|
||||
throw new OperationError("Character: " + elem + " is not a digit.");
|
||||
// If element is not a valid number in the given radix.
|
||||
if (isNaN(temp)) {
|
||||
throw new Error("Character: " + elem + " is not valid in radix " + radix + ".");
|
||||
}
|
||||
|
||||
// If element is in an even position
|
||||
if (even) {
|
||||
// Double the element and add the quotient and remainder together.
|
||||
temp = 2 * elem;
|
||||
temp = Math.floor(temp/10) + (temp % 10);
|
||||
// Double the element and sum the quotient and remainder.
|
||||
temp = 2 * temp;
|
||||
temp = Math.floor(temp / radix) + (temp % radix);
|
||||
}
|
||||
|
||||
even = !even;
|
||||
return acc + temp;
|
||||
}, 0) % 10;
|
||||
}, 0) % radix; // Use radix as the modulus base
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -63,9 +71,20 @@ class LuhnChecksum extends Operation {
|
|||
run(input, args) {
|
||||
if (!input) return "";
|
||||
|
||||
const checkSum = this.checksum(input);
|
||||
let checkDigit = this.checksum(input + "0");
|
||||
checkDigit = checkDigit === 0 ? 0 : (10-checkDigit);
|
||||
const radix = args[0];
|
||||
|
||||
if (radix < 2 || radix > 36) {
|
||||
throw new OperationError("Error: Radix argument must be between 2 and 36");
|
||||
}
|
||||
|
||||
if (radix % 2 !== 0) {
|
||||
throw new OperationError("Error: Radix argument must be divisible by 2");
|
||||
}
|
||||
|
||||
const checkSum = this.checksum(input, radix).toString(radix);
|
||||
let checkDigit = this.checksum(input + "0", radix);
|
||||
checkDigit = checkDigit === 0 ? 0 : (radix - checkDigit);
|
||||
checkDigit = checkDigit.toString(radix);
|
||||
|
||||
return `Checksum: ${checkSum}
|
||||
Checkdigit: ${checkDigit}
|
||||
|
|
171
src/core/operations/MIMEDecoding.mjs
Normal file
171
src/core/operations/MIMEDecoding.mjs
Normal file
|
@ -0,0 +1,171 @@
|
|||
/**
|
||||
* @author mshwed [m@ttshwed.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import { fromHex } from "../lib/Hex.mjs";
|
||||
import { fromBase64 } from "../lib/Base64.mjs";
|
||||
import cptable from "codepage";
|
||||
|
||||
/**
|
||||
* MIME Decoding operation
|
||||
*/
|
||||
class MIMEDecoding extends Operation {
|
||||
|
||||
/**
|
||||
* MIMEDecoding constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "MIME Decoding";
|
||||
this.module = "Default";
|
||||
this.description = "Enables the decoding of MIME message header extensions for non-ASCII text";
|
||||
this.infoURL = "https://tools.ietf.org/html/rfc2047";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const mimeEncodedText = Utils.byteArrayToUtf8(input);
|
||||
const encodedHeaders = mimeEncodedText.replace(/\r\n/g, "\n");
|
||||
|
||||
const decodedHeader = this.decodeHeaders(encodedHeaders);
|
||||
|
||||
return decodedHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode MIME header strings
|
||||
*
|
||||
* @param headerString
|
||||
*/
|
||||
decodeHeaders(headerString) {
|
||||
// No encoded words detected
|
||||
let i = headerString.indexOf("=?");
|
||||
if (i === -1) return headerString;
|
||||
|
||||
let decodedHeaders = headerString.slice(0, i);
|
||||
let header = headerString.slice(i);
|
||||
|
||||
let isBetweenWords = false;
|
||||
let start, cur, charset, encoding, j, end, text;
|
||||
while (header.length > -1) {
|
||||
start = header.indexOf("=?");
|
||||
if (start === -1) break;
|
||||
cur = start + "=?".length;
|
||||
|
||||
i = header.slice(cur).indexOf("?");
|
||||
if (i === -1) break;
|
||||
|
||||
charset = header.slice(cur, cur + i);
|
||||
cur += i + "?".length;
|
||||
|
||||
if (header.length < cur + "Q??=".length) break;
|
||||
|
||||
encoding = header[cur];
|
||||
cur += 1;
|
||||
|
||||
if (header[cur] !== "?") break;
|
||||
|
||||
cur += 1;
|
||||
|
||||
j = header.slice(cur).indexOf("?=");
|
||||
if (j === -1) break;
|
||||
|
||||
text = header.slice(cur, cur + j);
|
||||
end = cur + j + "?=".length;
|
||||
|
||||
if (encoding.toLowerCase() === "b") {
|
||||
text = fromBase64(text);
|
||||
} else if (encoding.toLowerCase() === "q") {
|
||||
text = this.parseQEncodedWord(text);
|
||||
} else {
|
||||
isBetweenWords = false;
|
||||
decodedHeaders += header.slice(0, start + 2);
|
||||
header = header.slice(start + 2);
|
||||
}
|
||||
|
||||
if (start > 0 && (!isBetweenWords || header.slice(0, start).search(/\S/g) > -1)) {
|
||||
decodedHeaders += header.slice(0, start);
|
||||
}
|
||||
|
||||
decodedHeaders += this.convertFromCharset(charset, text);
|
||||
|
||||
header = header.slice(end);
|
||||
isBetweenWords = true;
|
||||
}
|
||||
|
||||
if (header.length > 0) {
|
||||
decodedHeaders += header;
|
||||
}
|
||||
|
||||
return decodedHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts decoded text for supported charsets.
|
||||
* Supports UTF-8, US-ASCII, ISO-8859-*
|
||||
*
|
||||
* @param encodedWord
|
||||
*/
|
||||
convertFromCharset(charset, encodedText) {
|
||||
charset = charset.toLowerCase();
|
||||
const parsedCharset = charset.split("-");
|
||||
|
||||
if (parsedCharset.length === 2 && parsedCharset[0] === "utf" && charset === "utf-8") {
|
||||
return cptable.utils.decode(65001, encodedText);
|
||||
} else if (parsedCharset.length === 2 && charset === "us-ascii") {
|
||||
return cptable.utils.decode(20127, encodedText);
|
||||
} else if (parsedCharset.length === 3 && parsedCharset[0] === "iso" && parsedCharset[1] === "8859") {
|
||||
const isoCharset = parseInt(parsedCharset[2], 10);
|
||||
if (isoCharset >= 1 && isoCharset <= 16) {
|
||||
return cptable.utils.decode(28590 + isoCharset, encodedText);
|
||||
}
|
||||
}
|
||||
|
||||
throw new OperationError("Unhandled Charset");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a Q encoded word
|
||||
*
|
||||
* @param encodedWord
|
||||
*/
|
||||
parseQEncodedWord(encodedWord) {
|
||||
let decodedWord = "";
|
||||
for (let i = 0; i < encodedWord.length; i++) {
|
||||
if (encodedWord[i] === "_") {
|
||||
decodedWord += " ";
|
||||
// Parse hex encoding
|
||||
} else if (encodedWord[i] === "=") {
|
||||
if ((i + 2) >= encodedWord.length) throw new OperationError("Incorrectly Encoded Word");
|
||||
const decodedHex = Utils.byteArrayToChars(fromHex(encodedWord.substring(i + 1, i + 3)));
|
||||
decodedWord += decodedHex;
|
||||
i += 2;
|
||||
} else if (
|
||||
(encodedWord[i].charCodeAt(0) >= " ".charCodeAt(0) && encodedWord[i].charCodeAt(0) <= "~".charCodeAt(0)) ||
|
||||
encodedWord[i] === "\n" ||
|
||||
encodedWord[i] === "\r" ||
|
||||
encodedWord[i] === "\t") {
|
||||
decodedWord += encodedWord[i];
|
||||
} else {
|
||||
throw new OperationError("Incorrectly Encoded Word");
|
||||
}
|
||||
}
|
||||
|
||||
return decodedWord;
|
||||
}
|
||||
}
|
||||
|
||||
export default MIMEDecoding;
|
|
@ -8,7 +8,7 @@ import Operation from "../Operation.mjs";
|
|||
import OperationError from "../errors/OperationError.mjs";
|
||||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Normalise Image operation
|
||||
|
@ -43,7 +43,7 @@ class NormaliseImage extends Operation {
|
|||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error opening image file. (${err})`);
|
||||
}
|
||||
|
@ -53,9 +53,9 @@ class NormaliseImage extends Operation {
|
|||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
|
|
@ -12,9 +12,10 @@ import { isImage } from "../lib/FileType.mjs";
|
|||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
|
||||
import process from "process";
|
||||
import { createWorker } from "tesseract.js";
|
||||
|
||||
const OEM_MODES = ["Tesseract only", "LSTM only", "Tesseract/LSTM Combined"];
|
||||
|
||||
/**
|
||||
* Optical Character Recognition operation
|
||||
*/
|
||||
|
@ -37,6 +38,12 @@ class OpticalCharacterRecognition extends Operation {
|
|||
name: "Show confidence",
|
||||
type: "boolean",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: "OCR Engine Mode",
|
||||
type: "option",
|
||||
value: OEM_MODES,
|
||||
defaultIndex: 1
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -47,7 +54,7 @@ class OpticalCharacterRecognition extends Operation {
|
|||
* @returns {string}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [showConfidence] = args;
|
||||
const [showConfidence, oemChoice] = args;
|
||||
|
||||
if (!isWorkerEnvironment()) throw new OperationError("This operation only works in a browser");
|
||||
|
||||
|
@ -56,12 +63,13 @@ class OpticalCharacterRecognition extends Operation {
|
|||
throw new OperationError("Unsupported file type (supported: jpg,png,pbm,bmp) or no file provided");
|
||||
}
|
||||
|
||||
const assetDir = isWorkerEnvironment() ? `${self.docURL}/assets/` : `${process.cwd()}/src/core/vendor/`;
|
||||
const assetDir = `${self.docURL}/assets/`;
|
||||
const oem = OEM_MODES.indexOf(oemChoice);
|
||||
|
||||
try {
|
||||
self.sendStatusMessage("Spinning up Tesseract worker...");
|
||||
const image = `data:${type};base64,${toBase64(input)}`;
|
||||
const worker = createWorker({
|
||||
const worker = await createWorker("eng", oem, {
|
||||
workerPath: `${assetDir}tesseract/worker.min.js`,
|
||||
langPath: `${assetDir}tesseract/lang-data`,
|
||||
corePath: `${assetDir}tesseract/tesseract-core.wasm.js`,
|
||||
|
@ -71,11 +79,6 @@ class OpticalCharacterRecognition extends Operation {
|
|||
}
|
||||
}
|
||||
});
|
||||
await worker.load();
|
||||
self.sendStatusMessage(`Loading English language pack...`);
|
||||
await worker.loadLanguage("eng");
|
||||
self.sendStatusMessage("Intialising Tesseract API...");
|
||||
await worker.initialize("eng");
|
||||
self.sendStatusMessage("Finding text...");
|
||||
const result = await worker.recognize(image);
|
||||
|
||||
|
|
126
src/core/operations/PHPSerialize.mjs
Normal file
126
src/core/operations/PHPSerialize.mjs
Normal file
|
@ -0,0 +1,126 @@
|
|||
/**
|
||||
* @author brun0ne [brunonblok@gmail.com]
|
||||
* @copyright Crown Copyright 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* PHP Serialize operation
|
||||
*/
|
||||
class PHPSerialize extends Operation {
|
||||
|
||||
/**
|
||||
* PHPSerialize constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "PHP Serialize";
|
||||
this.module = "Default";
|
||||
this.description = "Performs PHP serialization on JSON data.<br><br>This function does not support <code>object</code> tags.<br><br>Since PHP doesn't distinguish dicts and arrays, this operation is not always symmetric to <code>PHP Deserialize</code>.<br><br>Example:<br><code>[5,"abc",true]</code><br>becomes<br><code>a:3:{i:0;i:5;i:1;s:3:"abc";i:2;b:1;}<code>";
|
||||
this.infoURL = "https://www.phpinternalsbook.com/php5/classes_objects/serialization.html";
|
||||
this.inputType = "JSON";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {JSON} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
/**
|
||||
* Determines if a number is an integer
|
||||
* @param {number} value
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isInteger(value) {
|
||||
return typeof value === "number" && parseInt(value.toString(), 10) === value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize basic types
|
||||
* @param {string | number | boolean} content
|
||||
* @returns {string}
|
||||
*/
|
||||
function serializeBasicTypes(content) {
|
||||
const basicTypes = {
|
||||
"string": "s",
|
||||
"integer": "i",
|
||||
"float": "d",
|
||||
"boolean": "b"
|
||||
};
|
||||
/**
|
||||
* Booleans
|
||||
* cast to 0 or 1
|
||||
*/
|
||||
if (typeof content === "boolean") {
|
||||
return `${basicTypes.boolean}:${content ? 1 : 0}`;
|
||||
}
|
||||
/* Numbers */
|
||||
if (typeof content === "number") {
|
||||
if (isInteger(content)) {
|
||||
return `${basicTypes.integer}:${content.toString()}`;
|
||||
} else {
|
||||
return `${basicTypes.float}:${content.toString()}`;
|
||||
}
|
||||
}
|
||||
/* Strings */
|
||||
if (typeof content === "string")
|
||||
return `${basicTypes.string}:${content.length}:"${content}"`;
|
||||
|
||||
/** This should be unreachable */
|
||||
throw new OperationError(`Encountered a non-implemented type: ${typeof content}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively serialize
|
||||
* @param {*} object
|
||||
* @returns {string}
|
||||
*/
|
||||
function serialize(object) {
|
||||
/* Null */
|
||||
if (object == null) {
|
||||
return `N;`;
|
||||
}
|
||||
|
||||
if (typeof object !== "object") {
|
||||
/* Basic types */
|
||||
return `${serializeBasicTypes(object)};`;
|
||||
} else if (object instanceof Array) {
|
||||
/* Arrays */
|
||||
const serializedElements = [];
|
||||
|
||||
for (let i = 0; i < object.length; i++) {
|
||||
serializedElements.push(`${serialize(i)}${serialize(object[i])}`);
|
||||
}
|
||||
|
||||
return `a:${object.length}:{${serializedElements.join("")}}`;
|
||||
} else if (object instanceof Object) {
|
||||
/**
|
||||
* Objects
|
||||
* Note: the output cannot be guaranteed to be in the same order as the input
|
||||
*/
|
||||
const serializedElements = [];
|
||||
const keys = Object.keys(object);
|
||||
|
||||
for (const key of keys) {
|
||||
serializedElements.push(`${serialize(key)}${serialize(object[key])}`);
|
||||
}
|
||||
|
||||
return `a:${keys.length}:{${serializedElements.join("")}}`;
|
||||
}
|
||||
|
||||
/** This should be unreachable */
|
||||
throw new OperationError(`Encountered a non-implemented type: ${typeof object}`);
|
||||
}
|
||||
|
||||
return serialize(input);
|
||||
}
|
||||
}
|
||||
|
||||
export default PHPSerialize;
|
|
@ -4,8 +4,9 @@
|
|||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import r from "jsrsasign";
|
||||
import Operation from "../Operation.mjs";
|
||||
import forge from "node-forge";
|
||||
import { formatDnObj } from "../lib/PublicKey.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
|
@ -30,16 +31,6 @@ class ParseCSR extends Operation {
|
|||
"name": "Input format",
|
||||
"type": "option",
|
||||
"value": ["PEM"]
|
||||
},
|
||||
{
|
||||
"name": "Key type",
|
||||
"type": "option",
|
||||
"value": ["RSA"]
|
||||
},
|
||||
{
|
||||
"name": "Strict ASN.1 value lengths",
|
||||
"type": "boolean",
|
||||
"value": true
|
||||
}
|
||||
];
|
||||
this.checks = [
|
||||
|
@ -61,73 +52,71 @@ class ParseCSR extends Operation {
|
|||
return "No input";
|
||||
}
|
||||
|
||||
const csr = forge.pki.certificationRequestFromPem(input, args[1]);
|
||||
// Parse the CSR into JSON parameters
|
||||
const csrParam = new r.KJUR.asn1.csr.CSRUtil.getParam(input);
|
||||
|
||||
// RSA algorithm is the only one supported for CSR in node-forge as of 1.3.1
|
||||
return `Version: ${1 + csr.version} (0x${Utils.hex(csr.version)})
|
||||
Subject${formatSubject(csr.subject)}
|
||||
Subject Alternative Names${formatSubjectAlternativeNames(csr)}
|
||||
Public Key
|
||||
Algorithm: RSA
|
||||
Length: ${csr.publicKey.n.bitLength()} bits
|
||||
Modulus: ${formatMultiLine(chop(csr.publicKey.n.toString(16).replace(/(..)/g, "$&:")))}
|
||||
Exponent: ${csr.publicKey.e} (0x${Utils.hex(csr.publicKey.e)})
|
||||
Signature
|
||||
Algorithm: ${forge.pki.oids[csr.signatureOid]}
|
||||
Signature: ${formatMultiLine(Utils.strToByteArray(csr.signature).map(b => Utils.hex(b)).join(":"))}
|
||||
Extensions${formatExtensions(csr)}`;
|
||||
return `Subject\n${formatDnObj(csrParam.subject, 2)}
|
||||
Public Key${formatSubjectPublicKey(csrParam.sbjpubkey)}
|
||||
Signature${formatSignature(csrParam.sigalg, csrParam.sighex)}
|
||||
Requested Extensions${formatRequestedExtensions(csrParam)}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format Subject of the request as a multi-line string
|
||||
* @param {*} subject CSR Subject
|
||||
* @returns Multi-line string describing Subject
|
||||
* Format signature of a CSR
|
||||
* @param {*} sigAlg string
|
||||
* @param {*} sigHex string
|
||||
* @returns Multi-line string describing CSR Signature
|
||||
*/
|
||||
function formatSubject(subject) {
|
||||
let out = "\n";
|
||||
function formatSignature(sigAlg, sigHex) {
|
||||
let out = `\n`;
|
||||
|
||||
for (const attribute of subject.attributes) {
|
||||
out += ` ${attribute.shortName} = ${attribute.value}\n`;
|
||||
out += ` Algorithm: ${sigAlg}\n`;
|
||||
|
||||
if (new RegExp("withdsa", "i").test(sigAlg)) {
|
||||
const d = new r.KJUR.crypto.DSA();
|
||||
const sigParam = d.parseASN1Signature(sigHex);
|
||||
out += ` Signature:
|
||||
R: ${formatHexOntoMultiLine(absBigIntToHex(sigParam[0]))}
|
||||
S: ${formatHexOntoMultiLine(absBigIntToHex(sigParam[1]))}\n`;
|
||||
} else if (new RegExp("withrsa", "i").test(sigAlg)) {
|
||||
out += ` Signature: ${formatHexOntoMultiLine(sigHex)}\n`;
|
||||
} else {
|
||||
out += ` Signature: ${formatHexOntoMultiLine(ensureHexIsPositiveInTwosComplement(sigHex))}\n`;
|
||||
}
|
||||
|
||||
return chop(out);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Format Subject Alternative Names from the name `subjectAltName` extension
|
||||
* @param {*} extension CSR object
|
||||
* @returns Multi-line string describing Subject Alternative Names
|
||||
* Format Subject Public Key from PEM encoded public key string
|
||||
* @param {*} publicKeyPEM string
|
||||
* @returns Multi-line string describing Subject Public Key Info
|
||||
*/
|
||||
function formatSubjectAlternativeNames(csr) {
|
||||
function formatSubjectPublicKey(publicKeyPEM) {
|
||||
let out = "\n";
|
||||
|
||||
for (const attribute of csr.attributes) {
|
||||
for (const extension of attribute.extensions) {
|
||||
if (extension.name === "subjectAltName") {
|
||||
const names = [];
|
||||
for (const altName of extension.altNames) {
|
||||
switch (altName.type) {
|
||||
case 1:
|
||||
names.push(`EMAIL: ${altName.value}`);
|
||||
break;
|
||||
case 2:
|
||||
names.push(`DNS: ${altName.value}`);
|
||||
break;
|
||||
case 6:
|
||||
names.push(`URI: ${altName.value}`);
|
||||
break;
|
||||
case 7:
|
||||
names.push(`IP: ${altName.ip}`);
|
||||
break;
|
||||
default:
|
||||
names.push(`(unable to format type ${altName.type} name)\n`);
|
||||
}
|
||||
}
|
||||
out += indent(2, names);
|
||||
}
|
||||
}
|
||||
const publicKey = r.KEYUTIL.getKey(publicKeyPEM);
|
||||
if (publicKey instanceof r.RSAKey) {
|
||||
out += ` Algorithm: RSA
|
||||
Length: ${publicKey.n.bitLength()} bits
|
||||
Modulus: ${formatHexOntoMultiLine(absBigIntToHex(publicKey.n))}
|
||||
Exponent: ${publicKey.e} (0x${Utils.hex(publicKey.e)})\n`;
|
||||
} else if (publicKey instanceof r.KJUR.crypto.ECDSA) {
|
||||
out += ` Algorithm: ECDSA
|
||||
Length: ${publicKey.ecparams.keylen} bits
|
||||
Pub: ${formatHexOntoMultiLine(publicKey.pubKeyHex)}
|
||||
ASN1 OID: ${r.KJUR.crypto.ECDSA.getName(publicKey.getShortNISTPCurveName())}
|
||||
NIST CURVE: ${publicKey.getShortNISTPCurveName()}\n`;
|
||||
} else if (publicKey instanceof r.KJUR.crypto.DSA) {
|
||||
out += ` Algorithm: DSA
|
||||
Length: ${publicKey.p.toString(16).length * 4} bits
|
||||
Pub: ${formatHexOntoMultiLine(absBigIntToHex(publicKey.y))}
|
||||
P: ${formatHexOntoMultiLine(absBigIntToHex(publicKey.p))}
|
||||
Q: ${formatHexOntoMultiLine(absBigIntToHex(publicKey.q))}
|
||||
G: ${formatHexOntoMultiLine(absBigIntToHex(publicKey.g))}\n`;
|
||||
} else {
|
||||
out += `unsupported public key algorithm\n`;
|
||||
}
|
||||
|
||||
return chop(out);
|
||||
|
@ -135,45 +124,105 @@ function formatSubjectAlternativeNames(csr) {
|
|||
|
||||
/**
|
||||
* Format known extensions of a CSR
|
||||
* @param {*} csr CSR object
|
||||
* @returns Multi-line string describing attributes
|
||||
* @param {*} csrParam object
|
||||
* @returns Multi-line string describing CSR Requested Extensions
|
||||
*/
|
||||
function formatExtensions(csr) {
|
||||
let out = "\n";
|
||||
function formatRequestedExtensions(csrParam) {
|
||||
const formattedExtensions = new Array(4).fill("");
|
||||
|
||||
for (const attribute of csr.attributes) {
|
||||
for (const extension of attribute.extensions) {
|
||||
// formatted separately
|
||||
if (extension.name === "subjectAltName") {
|
||||
continue;
|
||||
}
|
||||
out += ` ${extension.name}${(extension.critical ? " CRITICAL" : "")}:\n`;
|
||||
if (Object.hasOwn(csrParam, "extreq")) {
|
||||
for (const extension of csrParam.extreq) {
|
||||
let parts = [];
|
||||
switch (extension.name) {
|
||||
switch (extension.extname) {
|
||||
case "basicConstraints" :
|
||||
parts = describeBasicConstraints(extension);
|
||||
formattedExtensions[0] = ` Basic Constraints:${formatExtensionCriticalTag(extension)}\n${indent(4, parts)}`;
|
||||
break;
|
||||
case "keyUsage" :
|
||||
parts = describeKeyUsage(extension);
|
||||
formattedExtensions[1] = ` Key Usage:${formatExtensionCriticalTag(extension)}\n${indent(4, parts)}`;
|
||||
break;
|
||||
case "extKeyUsage" :
|
||||
parts = describeExtendedKeyUsage(extension);
|
||||
formattedExtensions[2] = ` Extended Key Usage:${formatExtensionCriticalTag(extension)}\n${indent(4, parts)}`;
|
||||
break;
|
||||
case "subjectAltName" :
|
||||
parts = describeSubjectAlternativeName(extension);
|
||||
formattedExtensions[3] = ` Subject Alternative Name:${formatExtensionCriticalTag(extension)}\n${indent(4, parts)}`;
|
||||
break;
|
||||
default :
|
||||
parts = ["(unable to format extension)"];
|
||||
parts = ["(unsuported extension)"];
|
||||
formattedExtensions.push(` ${extension.extname}:${formatExtensionCriticalTag(extension)}\n${indent(4, parts)}`);
|
||||
}
|
||||
out += indent(4, parts);
|
||||
}
|
||||
}
|
||||
|
||||
let out = "\n";
|
||||
|
||||
formattedExtensions.forEach((formattedExtension) => {
|
||||
if (formattedExtension !== undefined && formattedExtension !== null && formattedExtension.length !== 0) {
|
||||
out += formattedExtension;
|
||||
}
|
||||
});
|
||||
|
||||
return chop(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format extension critical tag
|
||||
* @param {*} extension Object
|
||||
* @returns String describing whether the extension is critical or not
|
||||
*/
|
||||
function formatExtensionCriticalTag(extension) {
|
||||
return Object.hasOwn(extension, "critical") && extension.critical ? " critical" : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Format hex string onto multiple lines
|
||||
* Format string input as a comma separated hex string on multiple lines
|
||||
* @param {*} hex String
|
||||
* @returns Multi-line string describing the Hex input
|
||||
*/
|
||||
function formatHexOntoMultiLine(hex) {
|
||||
if (hex.length % 2 !== 0) {
|
||||
hex = "0" + hex;
|
||||
}
|
||||
|
||||
return formatMultiLine(chop(hex.replace(/(..)/g, "$&:")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert BigInt to abs value in Hex
|
||||
* @param {*} int BigInt
|
||||
* @returns String representing absolute value in Hex
|
||||
*/
|
||||
function absBigIntToHex(int) {
|
||||
int = int < 0n ? -int : int;
|
||||
|
||||
return ensureHexIsPositiveInTwosComplement(int.toString(16));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure Hex String remains positive in 2's complement
|
||||
* @param {*} hex String
|
||||
* @returns Hex String ensuring value remains positive in 2's complement
|
||||
*/
|
||||
function ensureHexIsPositiveInTwosComplement(hex) {
|
||||
if (hex.length % 2 !== 0) {
|
||||
return "0" + hex;
|
||||
}
|
||||
|
||||
// prepend 00 if most significant bit is 1 (sign bit)
|
||||
if (hex.length >=2 && (parseInt(hex.substring(0, 2), 16) & 128)) {
|
||||
hex = "00" + hex;
|
||||
}
|
||||
|
||||
return hex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format string onto multiple lines
|
||||
* @param {*} longStr
|
||||
* @returns Hex string as a multi-line hex string
|
||||
* @returns String as a multi-line string
|
||||
*/
|
||||
function formatMultiLine(longStr) {
|
||||
const lines = [];
|
||||
|
@ -194,8 +243,8 @@ function formatMultiLine(longStr) {
|
|||
function describeBasicConstraints(extension) {
|
||||
const constraints = [];
|
||||
|
||||
constraints.push(`CA = ${extension.cA}`);
|
||||
if (extension.pathLenConstraint !== undefined) constraints.push(`PathLenConstraint = ${extension.pathLenConstraint}`);
|
||||
constraints.push(`CA = ${Object.hasOwn(extension, "cA") && extension.cA ? "true" : "false"}`);
|
||||
if (Object.hasOwn(extension, "pathLen")) constraints.push(`PathLenConstraint = ${extension.pathLen}`);
|
||||
|
||||
return constraints;
|
||||
}
|
||||
|
@ -209,15 +258,27 @@ function describeBasicConstraints(extension) {
|
|||
function describeKeyUsage(extension) {
|
||||
const usage = [];
|
||||
|
||||
if (extension.digitalSignature) usage.push("Digital signature");
|
||||
if (extension.nonRepudiation) usage.push("Non-repudiation");
|
||||
if (extension.keyEncipherment) usage.push("Key encipherment");
|
||||
if (extension.dataEncipherment) usage.push("Data encipherment");
|
||||
if (extension.keyAgreement) usage.push("Key agreement");
|
||||
if (extension.keyCertSign) usage.push("Key certificate signing");
|
||||
if (extension.cRLSign) usage.push("CRL signing");
|
||||
if (extension.encipherOnly) usage.push("Encipher only");
|
||||
if (extension.decipherOnly) usage.push("Decipher only");
|
||||
const kuIdentifierToName = {
|
||||
digitalSignature: "Digital Signature",
|
||||
nonRepudiation: "Non-repudiation",
|
||||
keyEncipherment: "Key encipherment",
|
||||
dataEncipherment: "Data encipherment",
|
||||
keyAgreement: "Key agreement",
|
||||
keyCertSign: "Key certificate signing",
|
||||
cRLSign: "CRL signing",
|
||||
encipherOnly: "Encipher Only",
|
||||
decipherOnly: "Decipher Only",
|
||||
};
|
||||
|
||||
if (Object.hasOwn(extension, "names")) {
|
||||
extension.names.forEach((ku) => {
|
||||
if (Object.hasOwn(kuIdentifierToName, ku)) {
|
||||
usage.push(kuIdentifierToName[ku]);
|
||||
} else {
|
||||
usage.push(`unknown key usage (${ku})`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (usage.length === 0) usage.push("(none)");
|
||||
|
||||
|
@ -233,23 +294,79 @@ function describeKeyUsage(extension) {
|
|||
function describeExtendedKeyUsage(extension) {
|
||||
const usage = [];
|
||||
|
||||
if (extension.serverAuth) usage.push("TLS Web Server Authentication");
|
||||
if (extension.clientAuth) usage.push("TLS Web Client Authentication");
|
||||
if (extension.codeSigning) usage.push("Code signing");
|
||||
if (extension.emailProtection) usage.push("E-mail Protection (S/MIME)");
|
||||
if (extension.timeStamping) usage.push("Trusted Timestamping");
|
||||
if (extension.msCodeInd) usage.push("Microsoft Individual Code Signing");
|
||||
if (extension.msCodeCom) usage.push("Microsoft Commercial Code Signing");
|
||||
if (extension.msCTLSign) usage.push("Microsoft Trust List Signing");
|
||||
if (extension.msSGC) usage.push("Microsoft Server Gated Crypto");
|
||||
if (extension.msEFS) usage.push("Microsoft Encrypted File System");
|
||||
if (extension.nsSGC) usage.push("Netscape Server Gated Crypto");
|
||||
const ekuIdentifierToName = {
|
||||
"serverAuth": "TLS Web Server Authentication",
|
||||
"clientAuth": "TLS Web Client Authentication",
|
||||
"codeSigning": "Code signing",
|
||||
"emailProtection": "E-mail Protection (S/MIME)",
|
||||
"timeStamping": "Trusted Timestamping",
|
||||
"1.3.6.1.4.1.311.2.1.21": "Microsoft Individual Code Signing", // msCodeInd
|
||||
"1.3.6.1.4.1.311.2.1.22": "Microsoft Commercial Code Signing", // msCodeCom
|
||||
"1.3.6.1.4.1.311.10.3.1": "Microsoft Trust List Signing", // msCTLSign
|
||||
"1.3.6.1.4.1.311.10.3.3": "Microsoft Server Gated Crypto", // msSGC
|
||||
"1.3.6.1.4.1.311.10.3.4": "Microsoft Encrypted File System", // msEFS
|
||||
"1.3.6.1.4.1.311.20.2.2": "Microsoft Smartcard Login", // msSmartcardLogin
|
||||
"2.16.840.1.113730.4.1": "Netscape Server Gated Crypto", // nsSGC
|
||||
};
|
||||
|
||||
if (Object.hasOwn(extension, "array")) {
|
||||
extension.array.forEach((eku) => {
|
||||
if (Object.hasOwn(ekuIdentifierToName, eku)) {
|
||||
usage.push(ekuIdentifierToName[eku]);
|
||||
} else {
|
||||
usage.push(eku);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (usage.length === 0) usage.push("(none)");
|
||||
|
||||
return usage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format Subject Alternative Names from the name `subjectAltName` extension
|
||||
* @see RFC 5280 4.2.1.6. Subject Alternative Name https://www.ietf.org/rfc/rfc5280.txt
|
||||
* @param {*} extension object
|
||||
* @returns Array of strings describing Subject Alternative Name extension
|
||||
*/
|
||||
function describeSubjectAlternativeName(extension) {
|
||||
const names = [];
|
||||
|
||||
if (Object.hasOwn(extension, "extname") && extension.extname === "subjectAltName") {
|
||||
if (Object.hasOwn(extension, "array")) {
|
||||
for (const altName of extension.array) {
|
||||
Object.keys(altName).forEach((key) => {
|
||||
switch (key) {
|
||||
case "rfc822":
|
||||
names.push(`EMAIL: ${altName[key]}`);
|
||||
break;
|
||||
case "dns":
|
||||
names.push(`DNS: ${altName[key]}`);
|
||||
break;
|
||||
case "uri":
|
||||
names.push(`URI: ${altName[key]}`);
|
||||
break;
|
||||
case "ip":
|
||||
names.push(`IP: ${altName[key]}`);
|
||||
break;
|
||||
case "dn":
|
||||
names.push(`DIR: ${altName[key].str}`);
|
||||
break;
|
||||
case "other" :
|
||||
names.push(`Other: ${altName[key].oid}::${altName[key].value.utf8str.str}`);
|
||||
break;
|
||||
default:
|
||||
names.push(`(unable to format SAN '${key}':${altName[key]})\n`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Join an array of strings and add leading spaces to each line.
|
||||
* @param {*} n How many leading spaces
|
||||
|
|
884
src/core/operations/ParseTLSRecord.mjs
Normal file
884
src/core/operations/ParseTLSRecord.mjs
Normal file
|
@ -0,0 +1,884 @@
|
|||
/**
|
||||
* @author c65722 []
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import {toHexFast} from "../lib/Hex.mjs";
|
||||
import {objToTable} from "../lib/Protocol.mjs";
|
||||
import Stream from "../lib/Stream.mjs";
|
||||
|
||||
/**
|
||||
* Parse TLS record operation.
|
||||
*/
|
||||
class ParseTLSRecord extends Operation {
|
||||
|
||||
/**
|
||||
* ParseTLSRecord constructor.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Parse TLS record";
|
||||
this.module = "Default";
|
||||
this.description = "Parses one or more TLS records";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Transport_Layer_Security";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "json";
|
||||
this.presentType = "html";
|
||||
this.args = [];
|
||||
this._handshakeParser = new HandshakeParser();
|
||||
this._contentTypes = new Map();
|
||||
|
||||
for (const key in ContentType) {
|
||||
this._contentTypes[ContentType[key]] = key.toString().toLocaleLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input - Stream, containing one or more raw TLS Records.
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} Array of Object representations of TLS Records contained within input.
|
||||
*/
|
||||
run(input, args) {
|
||||
const s = new Stream(new Uint8Array(input));
|
||||
|
||||
const output = [];
|
||||
|
||||
while (s.hasMore()) {
|
||||
const record = this._readRecord(s);
|
||||
if (record) {
|
||||
output.push(record);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a TLS Record from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw TLS Record.
|
||||
* @returns {Object} Object representation of TLS Record.
|
||||
*/
|
||||
_readRecord(input) {
|
||||
const RECORD_HEADER_LEN = 5;
|
||||
|
||||
if (input.position + RECORD_HEADER_LEN > input.length) {
|
||||
input.moveTo(input.length);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const type = input.readInt(1);
|
||||
const typeString = this._contentTypes[type] ?? type.toString();
|
||||
const version = "0x" + toHexFast(input.getBytes(2));
|
||||
const length = input.readInt(2);
|
||||
const content = input.getBytes(length);
|
||||
const truncated = content.length < length;
|
||||
|
||||
const recordHeader = new RecordHeader(typeString, version, length, truncated);
|
||||
|
||||
if (!content.length) {
|
||||
return {...recordHeader};
|
||||
}
|
||||
|
||||
if (type === ContentType.HANDSHAKE) {
|
||||
return this._handshakeParser.parse(new Stream(content), recordHeader);
|
||||
}
|
||||
|
||||
const record = {...recordHeader};
|
||||
record.value = "0x" + toHexFast(content);
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the parsed TLS Records in a tabular style.
|
||||
*
|
||||
* @param {Object[]} data - Array of Object representations of the TLS Records.
|
||||
* @returns {html} HTML representation of TLS Records contained within data.
|
||||
*/
|
||||
present(data) {
|
||||
return data.map(r => objToTable(r)).join("\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
export default ParseTLSRecord;
|
||||
|
||||
/**
|
||||
* Repesents the known values of type field of a TLS Record header.
|
||||
*/
|
||||
const ContentType = Object.freeze({
|
||||
CHANGE_CIPHER_SPEC: 20,
|
||||
ALERT: 21,
|
||||
HANDSHAKE: 22,
|
||||
APPLICATION_DATA: 23,
|
||||
});
|
||||
|
||||
/**
|
||||
* Represents a TLS Record header
|
||||
*/
|
||||
class RecordHeader {
|
||||
/**
|
||||
* RecordHeader cosntructor.
|
||||
*
|
||||
* @param {string} type - String representation of TLS Record type field.
|
||||
* @param {string} version - Hex representation of TLS Record version field.
|
||||
* @param {int} length - Length of TLS Record.
|
||||
* @param {bool} truncated - Is TLS Record truncated.
|
||||
*/
|
||||
constructor(type, version, length, truncated) {
|
||||
this.type = type;
|
||||
this.version = version;
|
||||
this.length = length;
|
||||
|
||||
if (truncated) {
|
||||
this.truncated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses TLS Handshake messages.
|
||||
*/
|
||||
class HandshakeParser {
|
||||
|
||||
/**
|
||||
* HandshakeParser constructor.
|
||||
*/
|
||||
constructor() {
|
||||
this._clientHelloParser = new ClientHelloParser();
|
||||
this._serverHelloParser = new ServerHelloParser();
|
||||
this._newSessionTicketParser = new NewSessionTicketParser();
|
||||
this._certificateParser = new CertificateParser();
|
||||
this._certificateRequestParser = new CertificateRequestParser();
|
||||
this._certificateVerifyParser = new CertificateVerifyParser();
|
||||
this._handshakeTypes = new Map();
|
||||
|
||||
for (const key in HandshakeType) {
|
||||
this._handshakeTypes[HandshakeType[key]] = key.toString().toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a single TLS handshake message.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw Handshake message.
|
||||
* @param {RecordHeader} recordHeader - TLS Record header.
|
||||
* @returns {Object} Object representation of Handshake.
|
||||
*/
|
||||
parse(input, recordHeader) {
|
||||
const output = {...recordHeader};
|
||||
|
||||
if (!input.hasMore()) {
|
||||
return output;
|
||||
}
|
||||
|
||||
const handshakeType = input.readInt(1);
|
||||
output.handshakeType = this._handshakeTypes[handshakeType] ?? handshakeType.toString();
|
||||
|
||||
if (input.position + 3 > input.length) {
|
||||
input.moveTo(input.length);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
const handshakeLength = input.readInt(3);
|
||||
|
||||
if (handshakeLength + 4 !== recordHeader.length) {
|
||||
input.moveTo(0);
|
||||
|
||||
output.handshakeType = this._handshakeTypes[HandshakeType.FINISHED];
|
||||
output.handshakeValue = "0x" + toHexFast(input.bytes);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
const content = input.getBytes(handshakeLength);
|
||||
if (!content.length) {
|
||||
return output;
|
||||
}
|
||||
|
||||
switch (handshakeType) {
|
||||
case HandshakeType.CLIENT_HELLO:
|
||||
return {...output, ...this._clientHelloParser.parse(new Stream(content))};
|
||||
case HandshakeType.SERVER_HELLO:
|
||||
return {...output, ...this._serverHelloParser.parse(new Stream(content))};
|
||||
case HandshakeType.NEW_SESSION_TICKET:
|
||||
return {...output, ...this._newSessionTicketParser.parse(new Stream(content))};
|
||||
case HandshakeType.CERTIFICATE:
|
||||
return {...output, ...this._certificateParser.parse(new Stream(content))};
|
||||
case HandshakeType.CERTIFICATE_REQUEST:
|
||||
return {...output, ...this._certificateRequestParser.parse(new Stream(content))};
|
||||
case HandshakeType.CERTIFICATE_VERIFY:
|
||||
return {...output, ...this._certificateVerifyParser.parse(new Stream(content))};
|
||||
default:
|
||||
output.handshakeValue = "0x" + toHexFast(content);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the known values of the msg_type field of a TLS Handshake message.
|
||||
*/
|
||||
const HandshakeType = Object.freeze({
|
||||
HELLO_REQUEST: 0,
|
||||
CLIENT_HELLO: 1,
|
||||
SERVER_HELLO: 2,
|
||||
NEW_SESSION_TICKET: 4,
|
||||
CERTIFICATE: 11,
|
||||
SERVER_KEY_EXCHANGE: 12,
|
||||
CERTIFICATE_REQUEST: 13,
|
||||
SERVER_HELLO_DONE: 14,
|
||||
CERTIFICATE_VERIFY: 15,
|
||||
CLIENT_KEY_EXCHANGE: 16,
|
||||
FINISHED: 20,
|
||||
});
|
||||
|
||||
/**
|
||||
* Parses TLS Handshake ClientHello messages.
|
||||
*/
|
||||
class ClientHelloParser {
|
||||
|
||||
/**
|
||||
* ClientHelloParser constructor.
|
||||
*/
|
||||
constructor() {
|
||||
this._extensionsParser = new ExtensionsParser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a single TLS Handshake ClientHello message.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ClientHello message.
|
||||
* @returns {Object} Object representation of ClientHello.
|
||||
*/
|
||||
parse(input) {
|
||||
const output = {};
|
||||
|
||||
output.clientVersion = this._readClientVersion(input);
|
||||
output.random = this._readRandom(input);
|
||||
|
||||
const sessionID = this._readSessionID(input);
|
||||
if (sessionID) {
|
||||
output.sessionID = sessionID;
|
||||
}
|
||||
|
||||
output.cipherSuites = this._readCipherSuites(input);
|
||||
output.compressionMethods = this._readCompressionMethods(input);
|
||||
output.extensions = this._readExtensions(input);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the client_version field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ClientHello message, with position before client_version field.
|
||||
* @returns {string} Hex representation of client_version.
|
||||
*/
|
||||
_readClientVersion(input) {
|
||||
return readBytesAsHex(input, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the random field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ClientHello message, with position before random field.
|
||||
* @returns {string} Hex representation of random.
|
||||
*/
|
||||
_readRandom(input) {
|
||||
return readBytesAsHex(input, 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the session_id field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ClientHello message, with position before session_id length field.
|
||||
* @returns {string} Hex representation of session_id, or empty string if session_id not present.
|
||||
*/
|
||||
_readSessionID(input) {
|
||||
return readSizePrefixedBytesAsHex(input, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the cipher_suites field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ClientHello message, with position before cipher_suites length field.
|
||||
* @returns {Object} Object represention of cipher_suites field.
|
||||
*/
|
||||
_readCipherSuites(input) {
|
||||
const output = {};
|
||||
|
||||
output.length = input.readInt(2);
|
||||
if (!output.length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const cipherSuites = new Stream(input.getBytes(output.length));
|
||||
if (cipherSuites.length < output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
output.values = [];
|
||||
|
||||
while (cipherSuites.hasMore()) {
|
||||
const cipherSuite = readBytesAsHex(cipherSuites, 2);
|
||||
if (cipherSuite) {
|
||||
output.values.push(cipherSuite);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the compression_methods field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ClientHello message, with position before compression_methods length field.
|
||||
* @returns {Object} Object representation of compression_methods field.
|
||||
*/
|
||||
_readCompressionMethods(input) {
|
||||
const output = {};
|
||||
|
||||
output.length = input.readInt(1);
|
||||
if (!output.length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const compressionMethods = new Stream(input.getBytes(output.length));
|
||||
if (compressionMethods.length < output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
output.values = [];
|
||||
|
||||
while (compressionMethods.hasMore()) {
|
||||
const compressionMethod = readBytesAsHex(compressionMethods, 1);
|
||||
if (compressionMethod) {
|
||||
output.values.push(compressionMethod);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the extensions field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ClientHello message, with position before extensions length field.
|
||||
* @returns {Object} Object representations of extensions field.
|
||||
*/
|
||||
_readExtensions(input) {
|
||||
const output = {};
|
||||
|
||||
output.length = input.readInt(2);
|
||||
if (!output.length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const extensions = new Stream(input.getBytes(output.length));
|
||||
if (extensions.length < output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
output.values = this._extensionsParser.parse(extensions);
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses TLS Handshake ServeHello messages.
|
||||
*/
|
||||
class ServerHelloParser {
|
||||
|
||||
/**
|
||||
* ServerHelloParser constructor.
|
||||
*/
|
||||
constructor() {
|
||||
this._extensionsParser = new ExtensionsParser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a single TLS Handshake ServerHello message.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ServerHello message.
|
||||
* @return {Object} Object representation of ServerHello.
|
||||
*/
|
||||
parse(input) {
|
||||
const output = {};
|
||||
|
||||
output.serverVersion = this._readServerVersion(input);
|
||||
output.random = this._readRandom(input);
|
||||
|
||||
const sessionID = this._readSessionID(input);
|
||||
if (sessionID) {
|
||||
output.sessionID = sessionID;
|
||||
}
|
||||
|
||||
output.cipherSuite = this._readCipherSuite(input);
|
||||
output.compressionMethod = this._readCompressionMethod(input);
|
||||
output.extensions = this._readExtensions(input);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the server_version field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ServerHello message, with position before server_version field.
|
||||
* @returns {string} Hex representation of server_version.
|
||||
*/
|
||||
_readServerVersion(input) {
|
||||
return readBytesAsHex(input, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the random field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ServerHello message, with position before random field.
|
||||
* @returns {string} Hex representation of random.
|
||||
*/
|
||||
_readRandom(input) {
|
||||
return readBytesAsHex(input, 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the session_id field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ServertHello message, with position before session_id length field.
|
||||
* @returns {string} Hex representation of session_id, or empty string if session_id not present.
|
||||
*/
|
||||
_readSessionID(input) {
|
||||
return readSizePrefixedBytesAsHex(input, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the cipher_suite field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ServerHello message, with position before cipher_suite field.
|
||||
* @returns {string} Hex represention of cipher_suite.
|
||||
*/
|
||||
_readCipherSuite(input) {
|
||||
return readBytesAsHex(input, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the compression_method field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ServerHello message, with position before compression_method field.
|
||||
* @returns {string} Hex represention of compression_method.
|
||||
*/
|
||||
_readCompressionMethod(input) {
|
||||
return readBytesAsHex(input, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the extensions field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ServerHello message, with position before extensions length field.
|
||||
* @returns {Object} Object representation of extensions field.
|
||||
*/
|
||||
_readExtensions(input) {
|
||||
const output = {};
|
||||
|
||||
output.length = input.readInt(2);
|
||||
if (!output.length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const extensions = new Stream(input.getBytes(output.length));
|
||||
if (extensions.length < output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
output.values = this._extensionsParser.parse(extensions);
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses TLS Handshake Hello Extensions.
|
||||
*/
|
||||
class ExtensionsParser {
|
||||
|
||||
/**
|
||||
* Parses a stream of TLS Handshake Hello Extensions.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing multiple raw Extensions, with position before first extension length field.
|
||||
* @returns {Object[]} Array of Object representations of Extensions contained within input.
|
||||
*/
|
||||
parse(input) {
|
||||
const output = [];
|
||||
|
||||
while (input.hasMore()) {
|
||||
const extension = this._readExtension(input);
|
||||
if (extension) {
|
||||
output.push(extension);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a single Extension from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a list of Extensions, with position before the length field of the next Extension.
|
||||
* @returns {Object} Object representation of Extension.
|
||||
*/
|
||||
_readExtension(input) {
|
||||
const output = {};
|
||||
|
||||
if (input.position + 4 > input.length) {
|
||||
input.moveTo(input.length);
|
||||
return null;
|
||||
}
|
||||
|
||||
output.type = "0x" + toHexFast(input.getBytes(2));
|
||||
output.length = input.readInt(2);
|
||||
if (!output.length) {
|
||||
return output;
|
||||
}
|
||||
|
||||
const value = input.getBytes(output.length);
|
||||
if (!value || value.length !== output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
if (value && value.length) {
|
||||
output.value = "0x" + toHexFast(value);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses TLS Handshake NewSessionTicket messages.
|
||||
*/
|
||||
class NewSessionTicketParser {
|
||||
|
||||
/**
|
||||
* Parses a single TLS Handshake NewSessionTicket message.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw NewSessionTicket message.
|
||||
* @returns {Object} Object representation of NewSessionTicket.
|
||||
*/
|
||||
parse(input) {
|
||||
return {
|
||||
ticketLifetimeHint: this._readTicketLifetimeHint(input),
|
||||
ticket: this._readTicket(input),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the ticket_lifetime_hint field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw NewSessionTicket message, with position before ticket_lifetime_hint field.
|
||||
* @returns {string} Lifetime hint, in seconds.
|
||||
*/
|
||||
_readTicketLifetimeHint(input) {
|
||||
if (input.position + 4 > input.length) {
|
||||
input.moveTo(input.length);
|
||||
return "";
|
||||
}
|
||||
|
||||
return input.readInt(4) + "s";
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the ticket field fromt the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw NewSessionTicket message, with position before ticket length field.
|
||||
* @returns {string} Hex representation of ticket.
|
||||
*/
|
||||
_readTicket(input) {
|
||||
return readSizePrefixedBytesAsHex(input, 2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses TLS Handshake Certificate messages.
|
||||
*/
|
||||
class CertificateParser {
|
||||
|
||||
/**
|
||||
* Parses a single TLS Handshake Certificate message.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw Certificate message.
|
||||
* @returns {Object} Object representation of Certificate.
|
||||
*/
|
||||
parse(input) {
|
||||
const output = {};
|
||||
|
||||
output.certificateList = this._readCertificateList(input);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the certificate_list field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw Certificate message, with position before certificate_list length field.
|
||||
* @returns {string[]} Array of strings, each containing a hex representation of a value within the certificate_list field.
|
||||
*/
|
||||
_readCertificateList(input) {
|
||||
const output = {};
|
||||
|
||||
if (input.position + 3 > input.length) {
|
||||
input.moveTo(input.length);
|
||||
return output;
|
||||
}
|
||||
|
||||
output.length = input.readInt(3);
|
||||
if (!output.length) {
|
||||
return output;
|
||||
}
|
||||
|
||||
const certificates = new Stream(input.getBytes(output.length));
|
||||
if (certificates.length < output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
output.values = [];
|
||||
|
||||
while (certificates.hasMore()) {
|
||||
const certificate = this._readCertificate(certificates);
|
||||
if (certificate) {
|
||||
output.values.push(certificate);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a single certificate from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a list of certificicates, with position before the length field of the next certificate.
|
||||
* @returns {string} Hex representation of certificate.
|
||||
*/
|
||||
_readCertificate(input) {
|
||||
return readSizePrefixedBytesAsHex(input, 3);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses TLS Handshake CertificateRequest messages.
|
||||
*/
|
||||
class CertificateRequestParser {
|
||||
|
||||
/**
|
||||
* Parses a single TLS Handshake CertificateRequest message.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw CertificateRequest message.
|
||||
* @return {Object} Object representation of CertificateRequest.
|
||||
*/
|
||||
parse(input) {
|
||||
const output = {};
|
||||
|
||||
output.certificateTypes = this._readCertificateTypes(input);
|
||||
output.supportedSignatureAlgorithms = this._readSupportedSignatureAlgorithms(input);
|
||||
|
||||
const certificateAuthorities = this._readCertificateAuthorities(input);
|
||||
if (certificateAuthorities.length) {
|
||||
output.certificateAuthorities = certificateAuthorities;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the certificate_types field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw CertificateRequest message, with position before certificate_types length field.
|
||||
* @return {string[]} Array of strings, each containing a hex representation of a value within the certificate_types field.
|
||||
*/
|
||||
_readCertificateTypes(input) {
|
||||
const output = {};
|
||||
|
||||
output.length = input.readInt(1);
|
||||
if (!output.length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const certificateTypes = new Stream(input.getBytes(output.length));
|
||||
if (certificateTypes.length < output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
output.values = [];
|
||||
|
||||
while (certificateTypes.hasMore()) {
|
||||
const certificateType = readBytesAsHex(certificateTypes, 1);
|
||||
if (certificateType) {
|
||||
output.values.push(certificateType);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the supported_signature_algorithms field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw CertificateRequest message, with position before supported_signature_algorithms length field.
|
||||
* @returns {string[]} Array of strings, each containing a hex representation of a value within the supported_signature_algorithms field.
|
||||
*/
|
||||
_readSupportedSignatureAlgorithms(input) {
|
||||
const output = {};
|
||||
|
||||
output.length = input.readInt(2);
|
||||
if (!output.length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const signatureAlgorithms = new Stream(input.getBytes(output.length));
|
||||
if (signatureAlgorithms.length < output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
output.values = [];
|
||||
|
||||
while (signatureAlgorithms.hasMore()) {
|
||||
const signatureAlgorithm = readBytesAsHex(signatureAlgorithms, 2);
|
||||
if (signatureAlgorithm) {
|
||||
output.values.push(signatureAlgorithm);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the certificate_authorities field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw CertificateRequest message, with position before certificate_authorities length field.
|
||||
* @returns {string[]} Array of strings, each containing a hex representation of a value within the certificate_authorities field.
|
||||
*/
|
||||
_readCertificateAuthorities(input) {
|
||||
const output = {};
|
||||
|
||||
output.length = input.readInt(2);
|
||||
if (!output.length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const certificateAuthorities = new Stream(input.getBytes(output.length));
|
||||
if (certificateAuthorities.length < output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
output.values = [];
|
||||
|
||||
while (certificateAuthorities.hasMore()) {
|
||||
const certificateAuthority = this._readCertificateAuthority(certificateAuthorities);
|
||||
if (certificateAuthority) {
|
||||
output.values.push(certificateAuthority);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a single certificate authority from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a list of raw certificate authorities, with position before the length field of the next certificate authority.
|
||||
* @returns {string} Hex representation of certificate authority.
|
||||
*/
|
||||
_readCertificateAuthority(input) {
|
||||
return readSizePrefixedBytesAsHex(input, 2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses TLS Handshake CertificateVerify messages.
|
||||
*/
|
||||
class CertificateVerifyParser {
|
||||
|
||||
/**
|
||||
* Parses a single CertificateVerify Message.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw CertificateVerify message.
|
||||
* @returns {Object} Object representation of CertificateVerify.
|
||||
*/
|
||||
parse(input) {
|
||||
return {
|
||||
algorithmHash: this._readAlgorithmHash(input),
|
||||
algorithmSignature: this._readAlgorithmSignature(input),
|
||||
signature: this._readSignature(input),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the algorithm.hash field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw CertificateVerify message, with position before algorithm.hash field.
|
||||
* @return {string} Hex representation of hash algorithm.
|
||||
*/
|
||||
_readAlgorithmHash(input) {
|
||||
return readBytesAsHex(input, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the algorithm.signature field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw CertificateVerify message, with position before algorithm.signature field.
|
||||
* @return {string} Hex representation of signature algorithm.
|
||||
*/
|
||||
_readAlgorithmSignature(input) {
|
||||
return readBytesAsHex(input, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the signature field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw CertificateVerify message, with position before signature field.
|
||||
* @return {string} Hex representation of signature.
|
||||
*/
|
||||
_readSignature(input) {
|
||||
return readSizePrefixedBytesAsHex(input, 2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the following size prefixed bytes from the provided Stream, and reuturn as a hex string.
|
||||
*
|
||||
* @param {Stream} input - Stream to read from.
|
||||
* @param {int} sizePrefixLength - Length of the size prefix field.
|
||||
* @returns {string} Hex representation of bytes read from Stream, empty string is returned if
|
||||
* field cannot be read in full.
|
||||
*/
|
||||
function readSizePrefixedBytesAsHex(input, sizePrefixLength) {
|
||||
const length = input.readInt(sizePrefixLength);
|
||||
if (!length) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return readBytesAsHex(input, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read n bytes from the provided Stream, and return as a hex string.
|
||||
*
|
||||
* @param {Stream} input - Stream to read from.
|
||||
* @param {int} n - Number of bytes to read.
|
||||
* @returns {string} Hex representation of bytes read from Stream, or empty string if field cannot
|
||||
* be read in full.
|
||||
*/
|
||||
function readBytesAsHex(input, n) {
|
||||
const bytes = input.getBytes(n);
|
||||
if (!bytes || bytes.length !== n) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return "0x" + toHexFast(bytes);
|
||||
}
|
391
src/core/operations/ParseX509CRL.mjs
Normal file
391
src/core/operations/ParseX509CRL.mjs
Normal file
|
@ -0,0 +1,391 @@
|
|||
/**
|
||||
* @author robinsandhu
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import r from "jsrsasign";
|
||||
import Operation from "../Operation.mjs";
|
||||
import { fromBase64 } from "../lib/Base64.mjs";
|
||||
import { toHex } from "../lib/Hex.mjs";
|
||||
import { formatDnObj } from "../lib/PublicKey.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* Parse X.509 CRL operation
|
||||
*/
|
||||
class ParseX509CRL extends Operation {
|
||||
|
||||
/**
|
||||
* ParseX509CRL constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Parse X.509 CRL";
|
||||
this.module = "PublicKey";
|
||||
this.description = "Parse Certificate Revocation List (CRL)";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Certificate_revocation_list";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Input format",
|
||||
"type": "option",
|
||||
"value": ["PEM", "DER Hex", "Base64", "Raw"]
|
||||
}
|
||||
];
|
||||
this.checks = [
|
||||
{
|
||||
"pattern": "^-+BEGIN X509 CRL-+\\r?\\n[\\da-z+/\\n\\r]+-+END X509 CRL-+\\r?\\n?$",
|
||||
"flags": "i",
|
||||
"args": ["PEM"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string} Human-readable description of a Certificate Revocation List (CRL).
|
||||
*/
|
||||
run(input, args) {
|
||||
if (!input.length) {
|
||||
return "No input";
|
||||
}
|
||||
|
||||
const inputFormat = args[0];
|
||||
|
||||
let undefinedInputFormat = false;
|
||||
try {
|
||||
switch (inputFormat) {
|
||||
case "DER Hex":
|
||||
input = input.replace(/\s/g, "").toLowerCase();
|
||||
break;
|
||||
case "PEM":
|
||||
break;
|
||||
case "Base64":
|
||||
input = toHex(fromBase64(input, null, "byteArray"), "");
|
||||
break;
|
||||
case "Raw":
|
||||
input = toHex(Utils.strToArrayBuffer(input), "");
|
||||
break;
|
||||
default:
|
||||
undefinedInputFormat = true;
|
||||
}
|
||||
} catch (e) {
|
||||
throw "Certificate load error (non-certificate input?)";
|
||||
}
|
||||
if (undefinedInputFormat) throw "Undefined input format";
|
||||
|
||||
const crl = new r.X509CRL(input);
|
||||
|
||||
let out = `Certificate Revocation List (CRL):
|
||||
Version: ${crl.getVersion() === null ? "1 (0x0)" : "2 (0x1)"}
|
||||
Signature Algorithm: ${crl.getSignatureAlgorithmField()}
|
||||
Issuer:\n${formatDnObj(crl.getIssuer(), 8)}
|
||||
Last Update: ${generalizedDateTimeToUTC(crl.getThisUpdate())}
|
||||
Next Update: ${generalizedDateTimeToUTC(crl.getNextUpdate())}\n`;
|
||||
|
||||
if (crl.getParam().ext !== undefined) {
|
||||
out += `\tCRL extensions:\n${formatCRLExtensions(crl.getParam().ext, 8)}\n`;
|
||||
}
|
||||
|
||||
out += `Revoked Certificates:\n${formatRevokedCertificates(crl.getRevCertArray(), 4)}
|
||||
Signature Value:\n${formatCRLSignature(crl.getSignatureValueHex(), 8)}`;
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generalized date time string to UTC.
|
||||
* @param {string} datetime
|
||||
* @returns UTC datetime string.
|
||||
*/
|
||||
function generalizedDateTimeToUTC(datetime) {
|
||||
// Ensure the string is in the correct format
|
||||
if (!/^\d{12,14}Z$/.test(datetime)) {
|
||||
throw new OperationError(`failed to format datetime string ${datetime}`);
|
||||
}
|
||||
|
||||
// Extract components
|
||||
let centuary = "20";
|
||||
if (datetime.length === 15) {
|
||||
centuary = datetime.substring(0, 2);
|
||||
datetime = datetime.slice(2);
|
||||
}
|
||||
const year = centuary + datetime.substring(0, 2);
|
||||
const month = datetime.substring(2, 4);
|
||||
const day = datetime.substring(4, 6);
|
||||
const hour = datetime.substring(6, 8);
|
||||
const minute = datetime.substring(8, 10);
|
||||
const second = datetime.substring(10, 12);
|
||||
|
||||
// Construct ISO 8601 format string
|
||||
const isoString = `${year}-${month}-${day}T${hour}:${minute}:${second}Z`;
|
||||
|
||||
// Parse using standard Date object
|
||||
const isoDateTime = new Date(isoString);
|
||||
|
||||
return isoDateTime.toUTCString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Format CRL extensions.
|
||||
* @param {r.ExtParam[] | undefined} extensions
|
||||
* @param {Number} indent
|
||||
* @returns Formatted string detailing CRL extensions.
|
||||
*/
|
||||
function formatCRLExtensions(extensions, indent) {
|
||||
if (Array.isArray(extensions) === false || extensions.length === 0) {
|
||||
return indentString(`No CRL extensions.`, indent);
|
||||
}
|
||||
|
||||
let out = ``;
|
||||
|
||||
extensions.sort((a, b) => {
|
||||
if (!Object.hasOwn(a, "extname") || !Object.hasOwn(b, "extname")) {
|
||||
return 0;
|
||||
}
|
||||
if (a.extname < b.extname) {
|
||||
return -1;
|
||||
} else if (a.extname === b.extname) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
|
||||
extensions.forEach((ext) => {
|
||||
if (!Object.hasOwn(ext, "extname")) {
|
||||
throw new OperationError(`CRL entry extension object missing 'extname' key: ${ext}`);
|
||||
}
|
||||
switch (ext.extname) {
|
||||
case "authorityKeyIdentifier":
|
||||
out += `X509v3 Authority Key Identifier:\n`;
|
||||
if (Object.hasOwn(ext, "kid")) {
|
||||
out += `\tkeyid:${colonDelimitedHexFormatString(ext.kid.hex.toUpperCase())}\n`;
|
||||
}
|
||||
if (Object.hasOwn(ext, "issuer")) {
|
||||
out += `\tDirName:${ext.issuer.str}\n`;
|
||||
}
|
||||
if (Object.hasOwn(ext, "sn")) {
|
||||
out += `\tserial:${colonDelimitedHexFormatString(ext.sn.hex.toUpperCase())}\n`;
|
||||
}
|
||||
break;
|
||||
case "cRLDistributionPoints":
|
||||
out += `X509v3 CRL Distribution Points:\n`;
|
||||
ext.array.forEach((distPoint) => {
|
||||
const fullName = `Full Name:\n${formatGeneralNames(distPoint.dpname.full, 4)}`;
|
||||
out += indentString(fullName, 4) + "\n";
|
||||
});
|
||||
break;
|
||||
case "cRLNumber":
|
||||
if (!Object.hasOwn(ext, "num")) {
|
||||
throw new OperationError(`'cRLNumber' CRL entry extension missing 'num' key: ${ext}`);
|
||||
}
|
||||
out += `X509v3 CRL Number:\n\t${ext.num.hex.toUpperCase()}\n`;
|
||||
break;
|
||||
case "issuerAltName":
|
||||
out += `X509v3 Issuer Alternative Name:\n${formatGeneralNames(ext.array, 4)}\n`;
|
||||
break;
|
||||
default:
|
||||
out += `${ext.extname}:\n`;
|
||||
out += `\tUnsupported CRL extension. Try openssl CLI.\n`;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return indentString(chop(out), indent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format general names array.
|
||||
* @param {Object[]} names
|
||||
* @returns Multi-line formatted string describing all supported general name types.
|
||||
*/
|
||||
function formatGeneralNames(names, indent) {
|
||||
let out = ``;
|
||||
|
||||
names.forEach((name) => {
|
||||
const key = Object.keys(name)[0];
|
||||
|
||||
switch (key) {
|
||||
case "ip":
|
||||
out += `IP:${name.ip}\n`;
|
||||
break;
|
||||
case "dns":
|
||||
out += `DNS:${name.dns}\n`;
|
||||
break;
|
||||
case "uri":
|
||||
out += `URI:${name.uri}\n`;
|
||||
break;
|
||||
case "rfc822":
|
||||
out += `EMAIL:${name.rfc822}\n`;
|
||||
break;
|
||||
case "dn":
|
||||
out += `DIR:${name.dn.str}\n`;
|
||||
break;
|
||||
case "other":
|
||||
out += `OtherName:${name.other.oid}::${Object.values(name.other.value)[0].str}\n`;
|
||||
break;
|
||||
default:
|
||||
out += `${key}: unsupported general name type`;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return indentString(chop(out), indent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Colon-delimited hex formatted output.
|
||||
* @param {string} hexString Hex String
|
||||
* @returns String representing input hex string with colon delimiter.
|
||||
*/
|
||||
function colonDelimitedHexFormatString(hexString) {
|
||||
if (hexString.length % 2 !== 0) {
|
||||
hexString = "0" + hexString;
|
||||
}
|
||||
|
||||
return chop(hexString.replace(/(..)/g, "$&:"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Format revoked certificates array
|
||||
* @param {r.RevokedCertificate[] | null} revokedCertificates
|
||||
* @param {Number} indent
|
||||
* @returns Multi-line formatted string output of revoked certificates array
|
||||
*/
|
||||
function formatRevokedCertificates(revokedCertificates, indent) {
|
||||
if (Array.isArray(revokedCertificates) === false || revokedCertificates.length === 0) {
|
||||
return indentString("No Revoked Certificates.", indent);
|
||||
}
|
||||
|
||||
let out=``;
|
||||
|
||||
revokedCertificates.forEach((revCert) => {
|
||||
if (!Object.hasOwn(revCert, "sn") || !Object.hasOwn(revCert, "date")) {
|
||||
throw new OperationError("invalid revoked certificate object, missing either serial number or date");
|
||||
}
|
||||
|
||||
out += `Serial Number: ${revCert.sn.hex.toUpperCase()}
|
||||
Revocation Date: ${generalizedDateTimeToUTC(revCert.date)}\n`;
|
||||
if (Object.hasOwn(revCert, "ext") && Array.isArray(revCert.ext) && revCert.ext.length !== 0) {
|
||||
out += `\tCRL entry extensions:\n${indentString(formatCRLEntryExtensions(revCert.ext), 2*indent)}\n`;
|
||||
}
|
||||
});
|
||||
|
||||
return indentString(chop(out), indent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format CRL entry extensions.
|
||||
* @param {Object[]} exts
|
||||
* @returns Formatted multi-line string describing CRL entry extensions.
|
||||
*/
|
||||
function formatCRLEntryExtensions(exts) {
|
||||
let out = ``;
|
||||
|
||||
const crlReasonCodeToReasonMessage = {
|
||||
0: "Unspecified",
|
||||
1: "Key Compromise",
|
||||
2: "CA Compromise",
|
||||
3: "Affiliation Changed",
|
||||
4: "Superseded",
|
||||
5: "Cessation Of Operation",
|
||||
6: "Certificate Hold",
|
||||
8: "Remove From CRL",
|
||||
9: "Privilege Withdrawn",
|
||||
10: "AA Compromise",
|
||||
};
|
||||
|
||||
const holdInstructionOIDToName = {
|
||||
"1.2.840.10040.2.1": "Hold Instruction None",
|
||||
"1.2.840.10040.2.2": "Hold Instruction Call Issuer",
|
||||
"1.2.840.10040.2.3": "Hold Instruction Reject",
|
||||
};
|
||||
|
||||
exts.forEach((ext) => {
|
||||
if (!Object.hasOwn(ext, "extname")) {
|
||||
throw new OperationError(`CRL entry extension object missing 'extname' key: ${ext}`);
|
||||
}
|
||||
switch (ext.extname) {
|
||||
case "cRLReason":
|
||||
if (!Object.hasOwn(ext, "code")) {
|
||||
throw new OperationError(`'cRLReason' CRL entry extension missing 'code' key: ${ext}`);
|
||||
}
|
||||
out += `X509v3 CRL Reason Code:
|
||||
${Object.hasOwn(crlReasonCodeToReasonMessage, ext.code) ? crlReasonCodeToReasonMessage[ext.code] : `invalid reason code: ${ext.code}`}\n`;
|
||||
break;
|
||||
case "2.5.29.23": // Hold instruction
|
||||
out += `Hold Instruction Code:\n\t${Object.hasOwn(holdInstructionOIDToName, ext.extn.oid) ? holdInstructionOIDToName[ext.extn.oid] : `${ext.extn.oid}: unknown hold instruction OID`}\n`;
|
||||
break;
|
||||
case "2.5.29.24": // Invalidity Date
|
||||
out += `Invalidity Date:\n\t${generalizedDateTimeToUTC(ext.extn.gentime.str)}\n`;
|
||||
break;
|
||||
default:
|
||||
out += `${ext.extname}:\n`;
|
||||
out += `\tUnsupported CRL entry extension. Try openssl CLI.\n`;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return chop(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format CRL signature.
|
||||
* @param {String} sigHex
|
||||
* @param {Number} indent
|
||||
* @returns String representing hex signature value formatted on multiple lines.
|
||||
*/
|
||||
function formatCRLSignature(sigHex, indent) {
|
||||
if (sigHex.length % 2 !== 0) {
|
||||
sigHex = "0" + sigHex;
|
||||
}
|
||||
|
||||
return indentString(formatMultiLine(chop(sigHex.replace(/(..)/g, "$&:"))), indent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format string onto multiple lines.
|
||||
* @param {string} longStr
|
||||
* @returns String as a multi-line string.
|
||||
*/
|
||||
function formatMultiLine(longStr) {
|
||||
const lines = [];
|
||||
|
||||
for (let remain = longStr ; remain !== "" ; remain = remain.substring(54)) {
|
||||
lines.push(remain.substring(0, 54));
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Indent a multi-line string by n spaces.
|
||||
* @param {string} input String
|
||||
* @param {number} spaces How many leading spaces
|
||||
* @returns Indented string.
|
||||
*/
|
||||
function indentString(input, spaces) {
|
||||
const indent = " ".repeat(spaces);
|
||||
return input.replace(/^/gm, indent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove last character from a string.
|
||||
* @param {string} s String
|
||||
* @returns Chopped string.
|
||||
*/
|
||||
function chop(s) {
|
||||
if (s.length < 1) {
|
||||
return s;
|
||||
}
|
||||
return s.substring(0, s.length - 1);
|
||||
}
|
||||
|
||||
export default ParseX509CRL;
|
|
@ -6,7 +6,8 @@
|
|||
|
||||
import r from "jsrsasign";
|
||||
import { fromBase64 } from "../lib/Base64.mjs";
|
||||
import { toHex } from "../lib/Hex.mjs";
|
||||
import { runHash } from "../lib/Hash.mjs";
|
||||
import { fromHex, toHex } from "../lib/Hex.mjs";
|
||||
import { formatByteStr, formatDnObj } from "../lib/PublicKey.mjs";
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
@ -81,7 +82,8 @@ class ParseX509Certificate extends Operation {
|
|||
}
|
||||
if (undefinedInputFormat) throw "Undefined input format";
|
||||
|
||||
const sn = cert.getSerialNumberHex(),
|
||||
const hex = Utils.strToArrayBuffer(Utils.byteArrayToChars(fromHex(cert.hex))),
|
||||
sn = cert.getSerialNumberHex(),
|
||||
issuer = cert.getIssuer(),
|
||||
subject = cert.getSubject(),
|
||||
pk = cert.getPublicKey(),
|
||||
|
@ -191,6 +193,10 @@ Issuer
|
|||
${issuerStr}
|
||||
Subject
|
||||
${subjectStr}
|
||||
Fingerprints
|
||||
MD5: ${runHash("md5", hex)}
|
||||
SHA1: ${runHash("sha1", hex)}
|
||||
SHA256: ${runHash("sha256", hex)}
|
||||
Public Key
|
||||
${pkStr.slice(0, -1)}
|
||||
Certificate Signature
|
||||
|
|
|
@ -59,15 +59,16 @@ class ROT13 extends Operation {
|
|||
rot13Upperacse = args[1],
|
||||
rotNumbers = args[2];
|
||||
let amount = args[3],
|
||||
chr;
|
||||
amountNumbers = args[3];
|
||||
|
||||
if (amount) {
|
||||
if (amount < 0) {
|
||||
amount = 26 - (Math.abs(amount) % 26);
|
||||
amountNumbers = 10 - (Math.abs(amountNumbers) % 10);
|
||||
}
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
chr = input[i];
|
||||
let chr = input[i];
|
||||
if (rot13Upperacse && chr >= 65 && chr <= 90) { // Upper case
|
||||
chr = (chr - 65 + amount) % 26;
|
||||
output[i] = chr + 65;
|
||||
|
@ -75,7 +76,7 @@ class ROT13 extends Operation {
|
|||
chr = (chr - 97 + amount) % 26;
|
||||
output[i] = chr + 97;
|
||||
} else if (rotNumbers && chr >= 48 && chr <= 57) { // Numbers
|
||||
chr = (chr - 48 + amount) % 10;
|
||||
chr = (chr - 48 + amountNumbers) % 10;
|
||||
output[i] = chr + 48;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ class RSASign extends Operation {
|
|||
const privateKey = forge.pki.decryptRsaPrivateKey(key, password);
|
||||
// Generate message hash
|
||||
const md = MD_ALGORITHMS[mdAlgo].create();
|
||||
md.update(input, "utf8");
|
||||
md.update(input, "raw");
|
||||
// Sign message hash
|
||||
const sig = privateKey.sign(md);
|
||||
return sig;
|
||||
|
|
|
@ -8,6 +8,7 @@ import Operation from "../Operation.mjs";
|
|||
import OperationError from "../errors/OperationError.mjs";
|
||||
import forge from "node-forge";
|
||||
import { MD_ALGORITHMS } from "../lib/RSA.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* RSA Verify operation
|
||||
|
@ -37,6 +38,11 @@ class RSAVerify extends Operation {
|
|||
type: "text",
|
||||
value: ""
|
||||
},
|
||||
{
|
||||
name: "Message format",
|
||||
type: "option",
|
||||
value: ["Raw", "Hex", "Base64"]
|
||||
},
|
||||
{
|
||||
name: "Message Digest Algorithm",
|
||||
type: "option",
|
||||
|
@ -51,7 +57,7 @@ class RSAVerify extends Operation {
|
|||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [pemKey, message, mdAlgo] = args;
|
||||
const [pemKey, message, format, mdAlgo] = args;
|
||||
if (pemKey.replace("-----BEGIN RSA PUBLIC KEY-----", "").length === 0) {
|
||||
throw new OperationError("Please enter a public key.");
|
||||
}
|
||||
|
@ -60,7 +66,8 @@ class RSAVerify extends Operation {
|
|||
const pubKey = forge.pki.publicKeyFromPem(pemKey);
|
||||
// Generate message digest
|
||||
const md = MD_ALGORITHMS[mdAlgo].create();
|
||||
md.update(message, "utf8");
|
||||
const messageStr = Utils.convertToByteString(message, format);
|
||||
md.update(messageStr, "raw");
|
||||
// Compare signed message digest and generated message digest
|
||||
const result = pubKey.verify(md.digest().bytes(), input);
|
||||
return result ? "Verified OK" : "Verification Failure";
|
||||
|
|
|
@ -10,7 +10,7 @@ import Utils from "../Utils.mjs";
|
|||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { runHash } from "../lib/Hash.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Randomize Colour Palette operation
|
||||
|
@ -48,7 +48,7 @@ class RandomizeColourPalette extends Operation {
|
|||
if (!isImage(input)) throw new OperationError("Please enter a valid image file.");
|
||||
|
||||
const seed = args[0] || (Math.random().toString().substr(2)),
|
||||
parsedImage = await jimp.read(input),
|
||||
parsedImage = await Jimp.read(input),
|
||||
width = parsedImage.bitmap.width,
|
||||
height = parsedImage.bitmap.height;
|
||||
|
||||
|
@ -61,7 +61,7 @@ class RandomizeColourPalette extends Operation {
|
|||
parsedImage.setPixelColor(parseInt(rgbHex, 16), x, y);
|
||||
});
|
||||
|
||||
const imageBuffer = await parsedImage.getBufferAsync(jimp.AUTO);
|
||||
const imageBuffer = await parsedImage.getBufferAsync(Jimp.AUTO);
|
||||
|
||||
return new Uint8Array(imageBuffer).buffer;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
|||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Resize Image operation
|
||||
|
@ -80,11 +80,11 @@ class ResizeImage extends Operation {
|
|||
resizeAlg = args[4];
|
||||
|
||||
const resizeMap = {
|
||||
"Nearest Neighbour": jimp.RESIZE_NEAREST_NEIGHBOR,
|
||||
"Bilinear": jimp.RESIZE_BILINEAR,
|
||||
"Bicubic": jimp.RESIZE_BICUBIC,
|
||||
"Hermite": jimp.RESIZE_HERMITE,
|
||||
"Bezier": jimp.RESIZE_BEZIER
|
||||
"Nearest Neighbour": Jimp.RESIZE_NEAREST_NEIGHBOR,
|
||||
"Bilinear": Jimp.RESIZE_BILINEAR,
|
||||
"Bicubic": Jimp.RESIZE_BICUBIC,
|
||||
"Hermite": Jimp.RESIZE_HERMITE,
|
||||
"Bezier": Jimp.RESIZE_BEZIER
|
||||
};
|
||||
|
||||
if (!isImage(input)) {
|
||||
|
@ -93,7 +93,7 @@ class ResizeImage extends Operation {
|
|||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
|
@ -113,9 +113,9 @@ class ResizeImage extends Operation {
|
|||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
|||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Rotate Image operation
|
||||
|
@ -52,7 +52,7 @@ class RotateImage extends Operation {
|
|||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
|
@ -63,9 +63,9 @@ class RotateImage extends Operation {
|
|||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
|
|
@ -40,7 +40,7 @@ class Sigaba extends Operation {
|
|||
value: false
|
||||
},
|
||||
{
|
||||
name: "1st cipher rotor intial value",
|
||||
name: "1st cipher rotor initial value",
|
||||
type: "option",
|
||||
value: LETTERS
|
||||
},
|
||||
|
@ -56,7 +56,7 @@ class Sigaba extends Operation {
|
|||
value: false
|
||||
},
|
||||
{
|
||||
name: "2nd cipher rotor intial value",
|
||||
name: "2nd cipher rotor initial value",
|
||||
type: "option",
|
||||
value: LETTERS
|
||||
},
|
||||
|
@ -72,7 +72,7 @@ class Sigaba extends Operation {
|
|||
value: false
|
||||
},
|
||||
{
|
||||
name: "3rd cipher rotor intial value",
|
||||
name: "3rd cipher rotor initial value",
|
||||
type: "option",
|
||||
value: LETTERS
|
||||
},
|
||||
|
@ -88,7 +88,7 @@ class Sigaba extends Operation {
|
|||
value: false
|
||||
},
|
||||
{
|
||||
name: "4th cipher rotor intial value",
|
||||
name: "4th cipher rotor initial value",
|
||||
type: "option",
|
||||
value: LETTERS
|
||||
},
|
||||
|
@ -104,7 +104,7 @@ class Sigaba extends Operation {
|
|||
value: false
|
||||
},
|
||||
{
|
||||
name: "5th cipher rotor intial value",
|
||||
name: "5th cipher rotor initial value",
|
||||
type: "option",
|
||||
value: LETTERS
|
||||
},
|
||||
|
@ -120,7 +120,7 @@ class Sigaba extends Operation {
|
|||
value: false
|
||||
},
|
||||
{
|
||||
name: "1st control rotor intial value",
|
||||
name: "1st control rotor initial value",
|
||||
type: "option",
|
||||
value: LETTERS
|
||||
},
|
||||
|
@ -136,7 +136,7 @@ class Sigaba extends Operation {
|
|||
value: false
|
||||
},
|
||||
{
|
||||
name: "2nd control rotor intial value",
|
||||
name: "2nd control rotor initial value",
|
||||
type: "option",
|
||||
value: LETTERS
|
||||
},
|
||||
|
@ -152,7 +152,7 @@ class Sigaba extends Operation {
|
|||
value: false
|
||||
},
|
||||
{
|
||||
name: "3rd control rotor intial value",
|
||||
name: "3rd control rotor initial value",
|
||||
type: "option",
|
||||
value: LETTERS
|
||||
},
|
||||
|
@ -168,7 +168,7 @@ class Sigaba extends Operation {
|
|||
value: false
|
||||
},
|
||||
{
|
||||
name: "4th control rotor intial value",
|
||||
name: "4th control rotor initial value",
|
||||
type: "option",
|
||||
value: LETTERS
|
||||
},
|
||||
|
@ -184,7 +184,7 @@ class Sigaba extends Operation {
|
|||
value: false
|
||||
},
|
||||
{
|
||||
name: "5th control rotor intial value",
|
||||
name: "5th control rotor initial value",
|
||||
type: "option",
|
||||
value: LETTERS
|
||||
},
|
||||
|
@ -195,7 +195,7 @@ class Sigaba extends Operation {
|
|||
defaultIndex: 0
|
||||
},
|
||||
{
|
||||
name: "1st index rotor intial value",
|
||||
name: "1st index rotor initial value",
|
||||
type: "option",
|
||||
value: NUMBERS
|
||||
},
|
||||
|
@ -206,7 +206,7 @@ class Sigaba extends Operation {
|
|||
defaultIndex: 0
|
||||
},
|
||||
{
|
||||
name: "2nd index rotor intial value",
|
||||
name: "2nd index rotor initial value",
|
||||
type: "option",
|
||||
value: NUMBERS
|
||||
},
|
||||
|
@ -217,7 +217,7 @@ class Sigaba extends Operation {
|
|||
defaultIndex: 0
|
||||
},
|
||||
{
|
||||
name: "3rd index rotor intial value",
|
||||
name: "3rd index rotor initial value",
|
||||
type: "option",
|
||||
value: NUMBERS
|
||||
},
|
||||
|
@ -228,7 +228,7 @@ class Sigaba extends Operation {
|
|||
defaultIndex: 0
|
||||
},
|
||||
{
|
||||
name: "4th index rotor intial value",
|
||||
name: "4th index rotor initial value",
|
||||
type: "option",
|
||||
value: NUMBERS
|
||||
},
|
||||
|
@ -239,7 +239,7 @@ class Sigaba extends Operation {
|
|||
defaultIndex: 0
|
||||
},
|
||||
{
|
||||
name: "5th index rotor intial value",
|
||||
name: "5th index rotor initial value",
|
||||
type: "option",
|
||||
value: NUMBERS
|
||||
},
|
||||
|
|
71
src/core/operations/SM2Decrypt.mjs
Normal file
71
src/core/operations/SM2Decrypt.mjs
Normal file
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* @author flakjacket95 [dflack95@gmail.com]
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Operation from "../Operation.mjs";
|
||||
|
||||
import { SM2 } from "../lib/SM2.mjs";
|
||||
|
||||
/**
|
||||
* SM2Decrypt operation
|
||||
*/
|
||||
class SM2Decrypt extends Operation {
|
||||
|
||||
/**
|
||||
* SM2Decrypt constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "SM2 Decrypt";
|
||||
this.module = "Crypto";
|
||||
this.description = "Decrypts a message utilizing the SM2 standard";
|
||||
this.infoURL = ""; // Usually a Wikipedia link. Remember to remove localisation (i.e. https://wikipedia.org/etc rather than https://en.wikipedia.org/etc)
|
||||
this.inputType = "string";
|
||||
this.outputType = "ArrayBuffer";
|
||||
this.args = [
|
||||
{
|
||||
name: "Private Key",
|
||||
type: "string",
|
||||
value: "DEADBEEF"
|
||||
},
|
||||
{
|
||||
"name": "Input Format",
|
||||
"type": "option",
|
||||
"value": ["C1C3C2", "C1C2C3"],
|
||||
"defaultIndex": 0
|
||||
},
|
||||
{
|
||||
name: "Curve",
|
||||
type: "option",
|
||||
"value": ["sm2p256v1"],
|
||||
"defaultIndex": 0
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [privateKey, inputFormat, curveName] = args;
|
||||
|
||||
if (privateKey.length !== 64) {
|
||||
throw new OperationError("Input private key must be in hex; and should be 32 bytes");
|
||||
}
|
||||
|
||||
const sm2 = new SM2(curveName, inputFormat);
|
||||
sm2.setPrivateKey(privateKey);
|
||||
|
||||
const result = sm2.decrypt(input);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default SM2Decrypt;
|
77
src/core/operations/SM2Encrypt.mjs
Normal file
77
src/core/operations/SM2Encrypt.mjs
Normal file
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* @author flakjacket95 [dflack95@gmail.com]
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Operation from "../Operation.mjs";
|
||||
|
||||
import { SM2 } from "../lib/SM2.mjs";
|
||||
|
||||
/**
|
||||
* SM2 Encrypt operation
|
||||
*/
|
||||
class SM2Encrypt extends Operation {
|
||||
|
||||
/**
|
||||
* SM2Encrypt constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "SM2 Encrypt";
|
||||
this.module = "Crypto";
|
||||
this.description = "Encrypts a message utilizing the SM2 standard";
|
||||
this.infoURL = ""; // Usually a Wikipedia link. Remember to remove localisation (i.e. https://wikipedia.org/etc rather than https://en.wikipedia.org/etc)
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "string";
|
||||
|
||||
this.args = [
|
||||
{
|
||||
name: "Public Key X",
|
||||
type: "string",
|
||||
value: "DEADBEEF"
|
||||
},
|
||||
{
|
||||
name: "Public Key Y",
|
||||
type: "string",
|
||||
value: "DEADBEEF"
|
||||
},
|
||||
{
|
||||
"name": "Output Format",
|
||||
"type": "option",
|
||||
"value": ["C1C3C2", "C1C2C3"],
|
||||
"defaultIndex": 0
|
||||
},
|
||||
{
|
||||
name: "Curve",
|
||||
type: "option",
|
||||
"value": ["sm2p256v1"],
|
||||
"defaultIndex": 0
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [publicKeyX, publicKeyY, outputFormat, curveName] = args;
|
||||
this.outputFormat = outputFormat;
|
||||
|
||||
if (publicKeyX.length !== 64 || publicKeyY.length !== 64) {
|
||||
throw new OperationError("Invalid Public Key - Ensure each component is 32 bytes in size and in hex");
|
||||
}
|
||||
|
||||
const sm2 = new SM2(curveName, outputFormat);
|
||||
sm2.setPublicKey(publicKeyX, publicKeyY);
|
||||
|
||||
const result = sm2.encrypt(new Uint8Array(input));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export default SM2Encrypt;
|
|
@ -10,7 +10,7 @@ import { isImage } from "../lib/FileType.mjs";
|
|||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { gaussianBlur } from "../lib/ImageManipulation.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Sharpen Image operation
|
||||
|
@ -68,7 +68,7 @@ class SharpenImage extends Operation {
|
|||
|
||||
let image;
|
||||
try {
|
||||
image = await jimp.read(input);
|
||||
image = await Jimp.read(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error loading image. (${err})`);
|
||||
}
|
||||
|
@ -137,9 +137,9 @@ class SharpenImage extends Operation {
|
|||
|
||||
let imageBuffer;
|
||||
if (image.getMIME() === "image/gif") {
|
||||
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
|
||||
} else {
|
||||
imageBuffer = await image.getBufferAsync(jimp.AUTO);
|
||||
imageBuffer = await image.getBufferAsync(Jimp.AUTO);
|
||||
}
|
||||
return imageBuffer.buffer;
|
||||
} catch (err) {
|
||||
|
|
|
@ -8,7 +8,7 @@ import Operation from "../Operation.mjs";
|
|||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import {isImage} from "../lib/FileType.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Split Colour Channels operation
|
||||
|
@ -41,7 +41,7 @@ class SplitColourChannels extends Operation {
|
|||
// Make sure that the input is an image
|
||||
if (!isImage(input)) throw new OperationError("Invalid file type.");
|
||||
|
||||
const parsedImage = await jimp.read(Buffer.from(input));
|
||||
const parsedImage = await Jimp.read(Buffer.from(input));
|
||||
|
||||
const red = new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
|
@ -51,7 +51,7 @@ class SplitColourChannels extends Operation {
|
|||
{apply: "blue", params: [-255]},
|
||||
{apply: "green", params: [-255]}
|
||||
])
|
||||
.getBufferAsync(jimp.MIME_PNG);
|
||||
.getBufferAsync(Jimp.MIME_PNG);
|
||||
resolve(new File([new Uint8Array((await split).values())], "red.png", {type: "image/png"}));
|
||||
} catch (err) {
|
||||
reject(new OperationError(`Could not split red channel: ${err}`));
|
||||
|
@ -64,7 +64,7 @@ class SplitColourChannels extends Operation {
|
|||
.color([
|
||||
{apply: "red", params: [-255]},
|
||||
{apply: "blue", params: [-255]},
|
||||
]).getBufferAsync(jimp.MIME_PNG);
|
||||
]).getBufferAsync(Jimp.MIME_PNG);
|
||||
resolve(new File([new Uint8Array((await split).values())], "green.png", {type: "image/png"}));
|
||||
} catch (err) {
|
||||
reject(new OperationError(`Could not split green channel: ${err}`));
|
||||
|
@ -77,7 +77,7 @@ class SplitColourChannels extends Operation {
|
|||
.color([
|
||||
{apply: "red", params: [-255]},
|
||||
{apply: "green", params: [-255]},
|
||||
]).getBufferAsync(jimp.MIME_PNG);
|
||||
]).getBufferAsync(Jimp.MIME_PNG);
|
||||
resolve(new File([new Uint8Array((await split).values())], "blue.png", {type: "image/png"}));
|
||||
} catch (err) {
|
||||
reject(new OperationError(`Could not split blue channel: ${err}`));
|
||||
|
|
57
src/core/operations/StripIPv4Header.mjs
Normal file
57
src/core/operations/StripIPv4Header.mjs
Normal file
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* @author c65722 []
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Stream from "../lib/Stream.mjs";
|
||||
|
||||
/**
|
||||
* Strip IPv4 header operation
|
||||
*/
|
||||
class StripIPv4Header extends Operation {
|
||||
|
||||
/**
|
||||
* StripIPv4Header constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Strip IPv4 header";
|
||||
this.module = "Default";
|
||||
this.description = "Strips the IPv4 header from an IPv4 packet, outputting the payload.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/IPv4";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "ArrayBuffer";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
run(input, args) {
|
||||
const MIN_HEADER_LEN = 20;
|
||||
|
||||
const s = new Stream(new Uint8Array(input));
|
||||
if (s.length < MIN_HEADER_LEN) {
|
||||
throw new OperationError("Input length is less than minimum IPv4 header length");
|
||||
}
|
||||
|
||||
const ihl = s.readInt(1) & 0x0f;
|
||||
const dataOffsetBytes = ihl * 4;
|
||||
if (s.length < dataOffsetBytes) {
|
||||
throw new OperationError("Input length is less than IHL");
|
||||
}
|
||||
|
||||
s.moveTo(dataOffsetBytes);
|
||||
|
||||
return s.getBytes().buffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default StripIPv4Header;
|
60
src/core/operations/StripTCPHeader.mjs
Normal file
60
src/core/operations/StripTCPHeader.mjs
Normal file
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
* @author c65722 []
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Stream from "../lib/Stream.mjs";
|
||||
|
||||
/**
|
||||
* Strip TCP header operation
|
||||
*/
|
||||
class StripTCPHeader extends Operation {
|
||||
|
||||
/**
|
||||
* StripTCPHeader constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Strip TCP header";
|
||||
this.module = "Default";
|
||||
this.description = "Strips the TCP header from a TCP segment, outputting the payload.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Transmission_Control_Protocol";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "ArrayBuffer";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
run(input, args) {
|
||||
const MIN_HEADER_LEN = 20;
|
||||
const DATA_OFFSET_OFFSET = 12;
|
||||
const DATA_OFFSET_LEN_BITS = 4;
|
||||
|
||||
const s = new Stream(new Uint8Array(input));
|
||||
if (s.length < MIN_HEADER_LEN) {
|
||||
throw new OperationError("Need at least 20 bytes for a TCP Header");
|
||||
}
|
||||
|
||||
s.moveTo(DATA_OFFSET_OFFSET);
|
||||
const dataOffsetWords = s.readBits(DATA_OFFSET_LEN_BITS);
|
||||
const dataOffsetBytes = dataOffsetWords * 4;
|
||||
if (s.length < dataOffsetBytes) {
|
||||
throw new OperationError("Input length is less than data offset");
|
||||
}
|
||||
|
||||
s.moveTo(dataOffsetBytes);
|
||||
|
||||
return s.getBytes().buffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default StripTCPHeader;
|
51
src/core/operations/StripUDPHeader.mjs
Normal file
51
src/core/operations/StripUDPHeader.mjs
Normal file
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* @author c65722 []
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Stream from "../lib/Stream.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* Strip UDP header operation
|
||||
*/
|
||||
class StripUDPHeader extends Operation {
|
||||
|
||||
/**
|
||||
* StripUDPHeader constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Strip UDP header";
|
||||
this.module = "Default";
|
||||
this.description = "Strips the UDP header from a UDP datagram, outputting the payload.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/User_Datagram_Protocol";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "ArrayBuffer";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
run(input, args) {
|
||||
const HEADER_LEN = 8;
|
||||
|
||||
const s = new Stream(new Uint8Array(input));
|
||||
if (s.length < HEADER_LEN) {
|
||||
throw new OperationError("Need 8 bytes for a UDP Header");
|
||||
}
|
||||
|
||||
s.moveTo(HEADER_LEN);
|
||||
|
||||
return s.getBytes().buffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default StripUDPHeader;
|
79
src/core/operations/TakeNthBytes.mjs
Normal file
79
src/core/operations/TakeNthBytes.mjs
Normal file
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* @author Oshawk [oshawk@protonmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* Take nth bytes operation
|
||||
*/
|
||||
class TakeNthBytes extends Operation {
|
||||
|
||||
/**
|
||||
* TakeNthBytes constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Take nth bytes";
|
||||
this.module = "Default";
|
||||
this.description = "Takes every nth byte starting with a given byte.";
|
||||
this.infoURL = "";
|
||||
this.inputType = "byteArray";
|
||||
this.outputType = "byteArray";
|
||||
this.args = [
|
||||
{
|
||||
name: "Take every",
|
||||
type: "number",
|
||||
value: 4
|
||||
},
|
||||
{
|
||||
name: "Starting at",
|
||||
type: "number",
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
name: "Apply to each line",
|
||||
type: "boolean",
|
||||
value: false
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {byteArray} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
run(input, args) {
|
||||
const n = args[0];
|
||||
const start = args[1];
|
||||
const eachLine = args[2];
|
||||
|
||||
if (parseInt(n, 10) !== n || n <= 0) {
|
||||
throw new OperationError("'Take every' must be a positive integer.");
|
||||
}
|
||||
if (parseInt(start, 10) !== start || start < 0) {
|
||||
throw new OperationError("'Starting at' must be a positive or zero integer.");
|
||||
}
|
||||
|
||||
let offset = 0;
|
||||
const output = [];
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
if (eachLine && input[i] === 0x0a) {
|
||||
output.push(0x0a);
|
||||
offset = i + 1;
|
||||
} else if (i - offset >= start && (i - (start + offset)) % n === 0) {
|
||||
output.push(input[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default TakeNthBytes;
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import {ALPHABET_OPTIONS} from "../lib/Base32.mjs";
|
||||
|
||||
/**
|
||||
* To Base32 operation
|
||||
|
@ -27,8 +28,8 @@ class ToBase32 extends Operation {
|
|||
this.args = [
|
||||
{
|
||||
name: "Alphabet",
|
||||
type: "binaryString",
|
||||
value: "A-Z2-7="
|
||||
type: "editableOption",
|
||||
value: ALPHABET_OPTIONS
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -83,3 +84,4 @@ class ToBase32 extends Operation {
|
|||
}
|
||||
|
||||
export default ToBase32;
|
||||
|
||||
|
|
55
src/core/operations/ToModhex.mjs
Normal file
55
src/core/operations/ToModhex.mjs
Normal file
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* @author linuxgemini [ilteris@asenkron.com.tr]
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { TO_MODHEX_DELIM_OPTIONS, toModhex } from "../lib/Modhex.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* To Modhex operation
|
||||
*/
|
||||
class ToModhex extends Operation {
|
||||
|
||||
/**
|
||||
* ToModhex constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "To Modhex";
|
||||
this.module = "Default";
|
||||
this.description = "Converts the input string to modhex bytes separated by the specified delimiter.";
|
||||
this.infoURL = "https://en.wikipedia.org/wiki/YubiKey#ModHex";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Delimiter",
|
||||
type: "option",
|
||||
value: TO_MODHEX_DELIM_OPTIONS
|
||||
},
|
||||
{
|
||||
name: "Bytes per line",
|
||||
type: "number",
|
||||
value: 0
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const delim = Utils.charRep(args[0]);
|
||||
const lineSize = args[1];
|
||||
|
||||
return toModhex(new Uint8Array(input), delim, 2, "", lineSize);
|
||||
}
|
||||
}
|
||||
|
||||
export default ToModhex;
|
|
@ -22,7 +22,7 @@ class TripleDESDecrypt extends Operation {
|
|||
|
||||
this.name = "Triple DES Decrypt";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Triple DES applies DES three times to each block to increase key size.<br><br><b>Key:</b> Triple DES uses a key length of 24 bytes (192 bits).<br>DES uses a key length of 8 bytes (64 bits).<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used as a default.";
|
||||
this.description = "Triple DES applies DES three times to each block to increase key size.<br><br><b>Key:</b> Triple DES uses a key length of 24 bytes (192 bits).<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used as a default.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Triple_DES";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
|
@ -73,8 +73,7 @@ class TripleDESDecrypt extends Operation {
|
|||
if (key.length !== 24 && key.length !== 16) {
|
||||
throw new OperationError(`Invalid key length: ${key.length} bytes
|
||||
|
||||
Triple DES uses a key length of 24 bytes (192 bits).
|
||||
DES uses a key length of 8 bytes (64 bits).`);
|
||||
Triple DES uses a key length of 24 bytes (192 bits).`);
|
||||
}
|
||||
if (iv.length !== 8 && mode !== "ECB") {
|
||||
throw new OperationError(`Invalid IV length: ${iv.length} bytes
|
||||
|
|
|
@ -22,7 +22,7 @@ class TripleDESEncrypt extends Operation {
|
|||
|
||||
this.name = "Triple DES Encrypt";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Triple DES applies DES three times to each block to increase key size.<br><br><b>Key:</b> Triple DES uses a key length of 24 bytes (192 bits).<br>DES uses a key length of 8 bytes (64 bits).<br><br>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.";
|
||||
this.description = "Triple DES applies DES three times to each block to increase key size.<br><br><b>Key:</b> Triple DES uses a key length of 24 bytes (192 bits).<br><br>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Triple_DES";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
|
@ -72,8 +72,7 @@ class TripleDESEncrypt extends Operation {
|
|||
if (key.length !== 24 && key.length !== 16) {
|
||||
throw new OperationError(`Invalid key length: ${key.length} bytes
|
||||
|
||||
Triple DES uses a key length of 24 bytes (192 bits).
|
||||
DES uses a key length of 8 bytes (64 bits).`);
|
||||
Triple DES uses a key length of 24 bytes (192 bits).`);
|
||||
}
|
||||
if (iv.length !== 8 && mode !== "ECB") {
|
||||
throw new OperationError(`Invalid IV length: ${iv.length} bytes
|
||||
|
|
|
@ -9,7 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
|||
import Utils from "../Utils.mjs";
|
||||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import jimp from "jimp";
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* View Bit Plane operation
|
||||
|
@ -52,7 +52,7 @@ class ViewBitPlane extends Operation {
|
|||
if (!isImage(input)) throw new OperationError("Please enter a valid image file.");
|
||||
|
||||
const [colour, bit] = args,
|
||||
parsedImage = await jimp.read(input),
|
||||
parsedImage = await Jimp.read(input),
|
||||
width = parsedImage.bitmap.width,
|
||||
height = parsedImage.bitmap.height,
|
||||
colourIndex = COLOUR_OPTIONS.indexOf(colour),
|
||||
|
@ -78,7 +78,7 @@ class ViewBitPlane extends Operation {
|
|||
|
||||
});
|
||||
|
||||
const imageBuffer = await parsedImage.getBufferAsync(jimp.AUTO);
|
||||
const imageBuffer = await parsedImage.getBufferAsync(Jimp.AUTO);
|
||||
|
||||
return new Uint8Array(imageBuffer).buffer;
|
||||
}
|
||||
|
|
|
@ -52,12 +52,6 @@ class XPathExpression extends Operation {
|
|||
try {
|
||||
doc = new xmldom.DOMParser({
|
||||
errorHandler: {
|
||||
warning(w) {
|
||||
throw w;
|
||||
},
|
||||
error(e) {
|
||||
throw e;
|
||||
},
|
||||
fatalError(e) {
|
||||
throw e;
|
||||
}
|
||||
|
|
45
src/core/operations/YAMLToJSON.mjs
Normal file
45
src/core/operations/YAMLToJSON.mjs
Normal file
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* @author ccarpo [ccarpo@gmx.net]
|
||||
* @copyright Crown Copyright 2021
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import jsYaml from "js-yaml";
|
||||
/**
|
||||
* YAML to JSON operation
|
||||
*/
|
||||
class YAMLToJSON extends Operation {
|
||||
|
||||
/**
|
||||
* YAMLToJSON constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "YAML to JSON";
|
||||
this.module = "Default";
|
||||
this.description = "Convert YAML to JSON";
|
||||
this.infoURL = "https://en.wikipedia.org/wiki/YAML";
|
||||
this.inputType = "string";
|
||||
this.outputType = "JSON";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {JSON}
|
||||
*/
|
||||
run(input, args) {
|
||||
try {
|
||||
return jsYaml.load(input);
|
||||
} catch (err) {
|
||||
throw new OperationError("Unable to parse YAML: " + err);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default YAMLToJSON;
|
4
src/core/vendor/DisassembleX86-64.mjs
vendored
4
src/core/vendor/DisassembleX86-64.mjs
vendored
|
@ -3199,7 +3199,7 @@ const REG = [
|
|||
REG index 10 Intel MM qword technology MMX vector instructions.
|
||||
---------------------------------------------------------------------------------------------------------------------------
|
||||
These can not be used with Vector length adjustment used in vector extensions. The MM register are the ST registers aliased
|
||||
to MM register. Instructions that use these registers use the the SIMD vector unit registers (MM), these are called the old
|
||||
to MM register. Instructions that use these registers use the SIMD vector unit registers (MM), these are called the old
|
||||
MMX vector instructions. When Intel added the SSE instructions to the SIMD math vector unit the new 128 bit XMM registers,
|
||||
are added into the SIMD unit then they ware made longer in size 256, then 512 across in length, with 1024 (?MM Reserved)
|
||||
In which the vector length setting was added to control there size though vector setting adjustment codes. Instruction
|
||||
|
@ -3784,7 +3784,7 @@ function GotoPosition( Address )
|
|||
/*-------------------------------------------------------------------------------------------------------------------------
|
||||
Finds bit positions to the Size attribute indexes in REG array, and the Pointer Array. For the Size Attribute variations.
|
||||
---------------------------------------------------------------------------------------------------------------------------
|
||||
The SizeAttribute settings is 8 digits big consisting of 1, or 0 to specify the the extended size that an operand can be made.
|
||||
The SizeAttribute settings is 8 digits big consisting of 1, or 0 to specify the extended size that an operand can be made.
|
||||
In which an value of 01100100 is decoded as "0 = 1024, 1 = 512, 1 = 256, 0 = 128, 0 = 64, 1 = 32, 0 = 16, 0 = 8".
|
||||
In which the largest bit position is 512, and is the 6th number "0 = 7, 1 = 6, 1 = 5, 0 = 4, 0 = 3, 1 = 2, 0 = 1, 0 = 0".
|
||||
In which 6 is the bit position for 512 as the returned Size . Each size is in order from 0 to 7, thus the size given back
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue