mirror of
https://github.com/gchq/CyberChef.git
synced 2025-04-22 07:46:16 -04:00
Merge branch 'master' into master
This commit is contained in:
commit
3d23a4f058
410 changed files with 40561 additions and 16354 deletions
|
@ -22,7 +22,7 @@ class AESDecrypt extends Operation {
|
|||
|
||||
this.name = "AES Decrypt";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Advanced Encryption Standard (AES) is a U.S. Federal Information Processing Standard (FIPS). It was selected after a 5-year process where 15 competing designs were evaluated.<br><br><b>Key:</b> The following algorithms will be used based on the size of the key:<ul><li>16 bytes = AES-128</li><li>24 bytes = AES-192</li><li>32 bytes = AES-256</li></ul><br><br><b>IV:</b> The Initialization Vector should be 16 bytes long. If not entered, it will default to 16 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.<br><br><b>GCM Tag:</b> This field is ignored unless 'GCM' mode is used.";
|
||||
this.description = "Advanced Encryption Standard (AES) is a U.S. Federal Information Processing Standard (FIPS). It was selected after a 5-year process where 15 competing designs were evaluated.<br><br><b>Key:</b> The following algorithms will be used based on the size of the key:<ul><li>16 bytes = AES-128</li><li>24 bytes = AES-192</li><li>32 bytes = AES-256</li></ul><br><br><b>IV:</b> The Initialization Vector should be 16 bytes long. If not entered, it will default to 16 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used as a default.<br><br><b>GCM Tag:</b> This field is ignored unless 'GCM' mode is used.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Advanced_Encryption_Standard";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
|
@ -66,6 +66,14 @@ class AESDecrypt extends Operation {
|
|||
{
|
||||
name: "ECB",
|
||||
off: [5, 6]
|
||||
},
|
||||
{
|
||||
name: "CBC/NoPadding",
|
||||
off: [5, 6]
|
||||
},
|
||||
{
|
||||
name: "ECB/NoPadding",
|
||||
off: [5, 6]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -104,7 +112,8 @@ class AESDecrypt extends Operation {
|
|||
run(input, args) {
|
||||
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].substring(0, 3),
|
||||
noPadding = args[2].endsWith("NoPadding"),
|
||||
inputType = args[3],
|
||||
outputType = args[4],
|
||||
gcmTag = Utils.convertToByteString(args[5].string, args[5].option),
|
||||
|
@ -122,6 +131,14 @@ The following algorithms will be used based on the size of the key:
|
|||
input = Utils.convertToByteString(input, inputType);
|
||||
|
||||
const decipher = forge.cipher.createDecipher("AES-" + mode, key);
|
||||
|
||||
/* Allow for a "no padding" mode */
|
||||
if (noPadding) {
|
||||
decipher.mode.unpad = function(output, options) {
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
decipher.start({
|
||||
iv: iv.length === 0 ? "" : iv,
|
||||
tag: mode === "GCM" ? gcmTag : undefined,
|
||||
|
|
128
src/core/operations/AESKeyUnwrap.mjs
Normal file
128
src/core/operations/AESKeyUnwrap.mjs
Normal file
|
@ -0,0 +1,128 @@
|
|||
/**
|
||||
* @author mikecat
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import { toHexFast } from "../lib/Hex.mjs";
|
||||
import forge from "node-forge";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* AES Key Unwrap operation
|
||||
*/
|
||||
class AESKeyUnwrap extends Operation {
|
||||
|
||||
/**
|
||||
* AESKeyUnwrap constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "AES Key Unwrap";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Decryptor for a key wrapping algorithm defined in RFC3394, which is used to protect keys in untrusted storage or communications, using AES.<br><br>This algorithm uses an AES key (KEK: key-encryption key) and a 64-bit IV to decrypt 64-bit blocks.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Key_wrap";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Key (KEK)",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "IV",
|
||||
"type": "toggleString",
|
||||
"value": "a6a6a6a6a6a6a6a6",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "Input",
|
||||
"type": "option",
|
||||
"value": ["Hex", "Raw"]
|
||||
},
|
||||
{
|
||||
"name": "Output",
|
||||
"type": "option",
|
||||
"value": ["Hex", "Raw"]
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const kek = Utils.convertToByteString(args[0].string, args[0].option),
|
||||
iv = Utils.convertToByteString(args[1].string, args[1].option),
|
||||
inputType = args[2],
|
||||
outputType = args[3];
|
||||
|
||||
if (kek.length !== 16 && kek.length !== 24 && kek.length !== 32) {
|
||||
throw new OperationError("KEK must be either 16, 24, or 32 bytes (currently " + kek.length + " bytes)");
|
||||
}
|
||||
if (iv.length !== 8) {
|
||||
throw new OperationError("IV must be 8 bytes (currently " + iv.length + " bytes)");
|
||||
}
|
||||
const inputData = Utils.convertToByteString(input, inputType);
|
||||
if (inputData.length % 8 !== 0 || inputData.length < 24) {
|
||||
throw new OperationError("input must be 8n (n>=3) bytes (currently " + inputData.length + " bytes)");
|
||||
}
|
||||
|
||||
const cipher = forge.cipher.createCipher("AES-ECB", kek);
|
||||
cipher.start();
|
||||
cipher.update(forge.util.createBuffer(""));
|
||||
cipher.finish();
|
||||
const paddingBlock = cipher.output.getBytes();
|
||||
|
||||
const decipher = forge.cipher.createDecipher("AES-ECB", kek);
|
||||
|
||||
let A = inputData.substring(0, 8);
|
||||
const R = [];
|
||||
for (let i = 8; i < inputData.length; i += 8) {
|
||||
R.push(inputData.substring(i, i + 8));
|
||||
}
|
||||
let cntLower = R.length >>> 0;
|
||||
let cntUpper = (R.length / ((1 << 30) * 4)) >>> 0;
|
||||
cntUpper = cntUpper * 6 + ((cntLower * 6 / ((1 << 30) * 4)) >>> 0);
|
||||
cntLower = cntLower * 6 >>> 0;
|
||||
for (let j = 5; j >= 0; j--) {
|
||||
for (let i = R.length - 1; i >= 0; i--) {
|
||||
const aBuffer = Utils.strToArrayBuffer(A);
|
||||
const aView = new DataView(aBuffer);
|
||||
aView.setUint32(0, aView.getUint32(0) ^ cntUpper);
|
||||
aView.setUint32(4, aView.getUint32(4) ^ cntLower);
|
||||
A = Utils.arrayBufferToStr(aBuffer, false);
|
||||
decipher.start();
|
||||
decipher.update(forge.util.createBuffer(A + R[i] + paddingBlock));
|
||||
decipher.finish();
|
||||
const B = decipher.output.getBytes();
|
||||
A = B.substring(0, 8);
|
||||
R[i] = B.substring(8, 16);
|
||||
cntLower--;
|
||||
if (cntLower < 0) {
|
||||
cntUpper--;
|
||||
cntLower = 0xffffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (A !== iv) {
|
||||
throw new OperationError("IV mismatch");
|
||||
}
|
||||
const P = R.join("");
|
||||
|
||||
if (outputType === "Hex") {
|
||||
return toHexFast(Utils.strToArrayBuffer(P));
|
||||
}
|
||||
return P;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default AESKeyUnwrap;
|
115
src/core/operations/AESKeyWrap.mjs
Normal file
115
src/core/operations/AESKeyWrap.mjs
Normal file
|
@ -0,0 +1,115 @@
|
|||
/**
|
||||
* @author mikecat
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import { toHexFast } from "../lib/Hex.mjs";
|
||||
import forge from "node-forge";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* AES Key Wrap operation
|
||||
*/
|
||||
class AESKeyWrap extends Operation {
|
||||
|
||||
/**
|
||||
* AESKeyWrap constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "AES Key Wrap";
|
||||
this.module = "Ciphers";
|
||||
this.description = "A key wrapping algorithm defined in RFC3394, which is used to protect keys in untrusted storage or communications, using AES.<br><br>This algorithm uses an AES key (KEK: key-encryption key) and a 64-bit IV to encrypt 64-bit blocks.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Key_wrap";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Key (KEK)",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "IV",
|
||||
"type": "toggleString",
|
||||
"value": "a6a6a6a6a6a6a6a6",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "Input",
|
||||
"type": "option",
|
||||
"value": ["Hex", "Raw"]
|
||||
},
|
||||
{
|
||||
"name": "Output",
|
||||
"type": "option",
|
||||
"value": ["Hex", "Raw"]
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const kek = Utils.convertToByteString(args[0].string, args[0].option),
|
||||
iv = Utils.convertToByteString(args[1].string, args[1].option),
|
||||
inputType = args[2],
|
||||
outputType = args[3];
|
||||
|
||||
if (kek.length !== 16 && kek.length !== 24 && kek.length !== 32) {
|
||||
throw new OperationError("KEK must be either 16, 24, or 32 bytes (currently " + kek.length + " bytes)");
|
||||
}
|
||||
if (iv.length !== 8) {
|
||||
throw new OperationError("IV must be 8 bytes (currently " + iv.length + " bytes)");
|
||||
}
|
||||
const inputData = Utils.convertToByteString(input, inputType);
|
||||
if (inputData.length % 8 !== 0 || inputData.length < 16) {
|
||||
throw new OperationError("input must be 8n (n>=2) bytes (currently " + inputData.length + " bytes)");
|
||||
}
|
||||
|
||||
const cipher = forge.cipher.createCipher("AES-ECB", kek);
|
||||
|
||||
let A = iv;
|
||||
const R = [];
|
||||
for (let i = 0; i < inputData.length; i += 8) {
|
||||
R.push(inputData.substring(i, i + 8));
|
||||
}
|
||||
let cntLower = 1, cntUpper = 0;
|
||||
for (let j = 0; j < 6; j++) {
|
||||
for (let i = 0; i < R.length; i++) {
|
||||
cipher.start();
|
||||
cipher.update(forge.util.createBuffer(A + R[i]));
|
||||
cipher.finish();
|
||||
const B = cipher.output.getBytes();
|
||||
const msbBuffer = Utils.strToArrayBuffer(B.substring(0, 8));
|
||||
const msbView = new DataView(msbBuffer);
|
||||
msbView.setUint32(0, msbView.getUint32(0) ^ cntUpper);
|
||||
msbView.setUint32(4, msbView.getUint32(4) ^ cntLower);
|
||||
A = Utils.arrayBufferToStr(msbBuffer, false);
|
||||
R[i] = B.substring(8, 16);
|
||||
cntLower++;
|
||||
if (cntLower > 0xffffffff) {
|
||||
cntUpper++;
|
||||
cntLower = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
const C = A + R.join("");
|
||||
|
||||
if (outputType === "Hex") {
|
||||
return toHexFast(Utils.strToArrayBuffer(C));
|
||||
}
|
||||
return C;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default AESKeyWrap;
|
52
src/core/operations/AMFDecode.mjs
Normal file
52
src/core/operations/AMFDecode.mjs
Normal file
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import "reflect-metadata"; // Required as a shim for the amf library
|
||||
import { AMF0, AMF3 } from "@astronautlabs/amf";
|
||||
|
||||
/**
|
||||
* AMF Decode operation
|
||||
*/
|
||||
class AMFDecode extends Operation {
|
||||
|
||||
/**
|
||||
* AMFDecode constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "AMF Decode";
|
||||
this.module = "Encodings";
|
||||
this.description = "Action Message Format (AMF) is a binary format used to serialize object graphs such as ActionScript objects and XML, or send messages between an Adobe Flash client and a remote service, usually a Flash Media Server or third party alternatives.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Action_Message_Format";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "JSON";
|
||||
this.args = [
|
||||
{
|
||||
name: "Format",
|
||||
type: "option",
|
||||
value: ["AMF0", "AMF3"],
|
||||
defaultIndex: 1
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {JSON}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [format] = args;
|
||||
const handler = format === "AMF0" ? AMF0 : AMF3;
|
||||
const encoded = new Uint8Array(input);
|
||||
return handler.Value.deserialize(encoded);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default AMFDecode;
|
52
src/core/operations/AMFEncode.mjs
Normal file
52
src/core/operations/AMFEncode.mjs
Normal file
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import "reflect-metadata"; // Required as a shim for the amf library
|
||||
import { AMF0, AMF3 } from "@astronautlabs/amf";
|
||||
|
||||
/**
|
||||
* AMF Encode operation
|
||||
*/
|
||||
class AMFEncode extends Operation {
|
||||
|
||||
/**
|
||||
* AMFEncode constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "AMF Encode";
|
||||
this.module = "Encodings";
|
||||
this.description = "Action Message Format (AMF) is a binary format used to serialize object graphs such as ActionScript objects and XML, or send messages between an Adobe Flash client and a remote service, usually a Flash Media Server or third party alternatives.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Action_Message_Format";
|
||||
this.inputType = "JSON";
|
||||
this.outputType = "ArrayBuffer";
|
||||
this.args = [
|
||||
{
|
||||
name: "Format",
|
||||
type: "option",
|
||||
value: ["AMF0", "AMF3"],
|
||||
defaultIndex: 1
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {JSON} input
|
||||
* @param {Object[]} args
|
||||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [format] = args;
|
||||
const handler = format === "AMF0" ? AMF0 : AMF3;
|
||||
const output = handler.Value.any(input).serialize();
|
||||
return output.buffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default AMFEncode;
|
|
@ -9,8 +9,7 @@ import OperationError from "../errors/OperationError.mjs";
|
|||
import { isImage } from "../lib/FileType.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import jimplib from "jimp/es/index.js";
|
||||
const jimp = jimplib.default ? jimplib.default : jimplib;
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Add Text To Image operation
|
||||
|
@ -128,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})`);
|
||||
}
|
||||
|
@ -164,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) {
|
||||
|
@ -191,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
|
||||
|
@ -199,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -235,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) {
|
||||
|
|
117
src/core/operations/Argon2.mjs
Normal file
117
src/core/operations/Argon2.mjs
Normal file
|
@ -0,0 +1,117 @@
|
|||
/**
|
||||
* @author Tan Zhen Yong [tzy@beyondthesprawl.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import argon2 from "argon2-browser";
|
||||
|
||||
/**
|
||||
* Argon2 operation
|
||||
*/
|
||||
class Argon2 extends Operation {
|
||||
|
||||
/**
|
||||
* Argon2 constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Argon2";
|
||||
this.module = "Crypto";
|
||||
this.description = "Argon2 is a key derivation function that was selected as the winner of the Password Hashing Competition in July 2015. It was designed by Alex Biryukov, Daniel Dinu, and Dmitry Khovratovich from the University of Luxembourg.<br><br>Enter the password in the input to generate its hash.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Argon2";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Salt",
|
||||
"type": "toggleString",
|
||||
"value": "somesalt",
|
||||
"toggleValues": ["UTF8", "Hex", "Base64", "Latin1"]
|
||||
},
|
||||
{
|
||||
"name": "Iterations",
|
||||
"type": "number",
|
||||
"value": 3
|
||||
},
|
||||
{
|
||||
"name": "Memory (KiB)",
|
||||
"type": "number",
|
||||
"value": 4096
|
||||
},
|
||||
{
|
||||
"name": "Parallelism",
|
||||
"type": "number",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "Hash length (bytes)",
|
||||
"type": "number",
|
||||
"value": 32
|
||||
},
|
||||
{
|
||||
"name": "Type",
|
||||
"type": "option",
|
||||
"value": ["Argon2i", "Argon2d", "Argon2id"],
|
||||
"defaultIndex": 0
|
||||
},
|
||||
{
|
||||
"name": "Output format",
|
||||
"type": "option",
|
||||
"value": ["Encoded hash", "Hex hash", "Raw hash"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const argon2Types = {
|
||||
"Argon2i": argon2.ArgonType.Argon2i,
|
||||
"Argon2d": argon2.ArgonType.Argon2d,
|
||||
"Argon2id": argon2.ArgonType.Argon2id
|
||||
};
|
||||
|
||||
const salt = Utils.convertToByteString(args[0].string || "", args[0].option),
|
||||
time = args[1],
|
||||
mem = args[2],
|
||||
parallelism = args[3],
|
||||
hashLen = args[4],
|
||||
type = argon2Types[args[5]],
|
||||
outFormat = args[6];
|
||||
|
||||
try {
|
||||
const result = await argon2.hash({
|
||||
pass: input,
|
||||
salt,
|
||||
time,
|
||||
mem,
|
||||
parallelism,
|
||||
hashLen,
|
||||
type,
|
||||
});
|
||||
|
||||
switch (outFormat) {
|
||||
case "Hex hash":
|
||||
return result.hashHex;
|
||||
case "Raw hash":
|
||||
return Utils.arrayBufferToStr(result.hash);
|
||||
case "Encoded hash":
|
||||
default:
|
||||
return result.encoded;
|
||||
}
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Argon2;
|
58
src/core/operations/Argon2Compare.mjs
Normal file
58
src/core/operations/Argon2Compare.mjs
Normal file
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* @author Tan Zhen Yong [tzy@beyondthesprawl.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import argon2 from "argon2-browser";
|
||||
|
||||
/**
|
||||
* Argon2 compare operation
|
||||
*/
|
||||
class Argon2Compare extends Operation {
|
||||
|
||||
/**
|
||||
* Argon2Compare constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Argon2 compare";
|
||||
this.module = "Crypto";
|
||||
this.description = "Tests whether the input matches the given Argon2 hash. To test multiple possible passwords, use the 'Fork' operation.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Argon2";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Encoded hash",
|
||||
"type": "string",
|
||||
"value": ""
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const encoded = args[0];
|
||||
|
||||
try {
|
||||
await argon2.verify({
|
||||
pass: input,
|
||||
encoded
|
||||
});
|
||||
|
||||
return `Match: ${input}`;
|
||||
} catch (err) {
|
||||
return "No match";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Argon2Compare;
|
|
@ -70,10 +70,14 @@ class BlowfishDecrypt extends Operation {
|
|||
inputType = args[3],
|
||||
outputType = args[4];
|
||||
|
||||
if (key.length !== 8) {
|
||||
if (key.length < 4 || key.length > 56) {
|
||||
throw new OperationError(`Invalid key length: ${key.length} bytes
|
||||
|
||||
Blowfish uses a key length of 8 bytes (64 bits).`);
|
||||
Blowfish's key length needs to be between 4 and 56 bytes (32-448 bits).`);
|
||||
}
|
||||
|
||||
if (iv.length !== 8) {
|
||||
throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes`);
|
||||
}
|
||||
|
||||
input = Utils.convertToByteString(input, inputType);
|
||||
|
|
|
@ -70,10 +70,14 @@ class BlowfishEncrypt extends Operation {
|
|||
inputType = args[3],
|
||||
outputType = args[4];
|
||||
|
||||
if (key.length !== 8) {
|
||||
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).`);
|
||||
}
|
||||
|
||||
Blowfish uses a key length of 8 bytes (64 bits).`);
|
||||
if (iv.length !== 8) {
|
||||
throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes`);
|
||||
}
|
||||
|
||||
input = Utils.convertToByteString(input, inputType);
|
||||
|
|
|
@ -10,8 +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 jimplib from "jimp/es/index.js";
|
||||
const jimp = jimplib.default ? jimplib.default : jimplib;
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Blur Image operation
|
||||
|
@ -60,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})`);
|
||||
}
|
||||
|
@ -80,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,6 +1,9 @@
|
|||
/**
|
||||
* Emulation of the Bombe machine.
|
||||
*
|
||||
* Tested against the Bombe Rebuild at Bletchley Park's TNMOC
|
||||
* using a variety of inputs and settings to confirm correctness.
|
||||
*
|
||||
* @author s2224834
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
|
|
149
src/core/operations/CMAC.mjs
Normal file
149
src/core/operations/CMAC.mjs
Normal file
|
@ -0,0 +1,149 @@
|
|||
/**
|
||||
* @author mikecat
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import forge from "node-forge";
|
||||
import { toHexFast } from "../lib/Hex.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* CMAC operation
|
||||
*/
|
||||
class CMAC extends Operation {
|
||||
|
||||
/**
|
||||
* CMAC constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "CMAC";
|
||||
this.module = "Crypto";
|
||||
this.description = "CMAC is a block-cipher based message authentication code algorithm.<br><br>RFC4493 defines AES-CMAC that uses AES encryption with a 128-bit key.<br>NIST SP 800-38B suggests usages of AES with other key lengths and Triple DES.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/CMAC";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Key",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "Encryption algorithm",
|
||||
"type": "option",
|
||||
"value": ["AES", "Triple DES"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const key = Utils.convertToByteString(args[0].string, args[0].option);
|
||||
const algo = args[1];
|
||||
|
||||
const info = (function() {
|
||||
switch (algo) {
|
||||
case "AES":
|
||||
if (key.length !== 16 && key.length !== 24 && key.length !== 32) {
|
||||
throw new OperationError("The key for AES must be either 16, 24, or 32 bytes (currently " + key.length + " bytes)");
|
||||
}
|
||||
return {
|
||||
"algorithm": "AES-ECB",
|
||||
"key": key,
|
||||
"blockSize": 16,
|
||||
"Rb": new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x87]),
|
||||
};
|
||||
case "Triple DES":
|
||||
if (key.length !== 16 && key.length !== 24) {
|
||||
throw new OperationError("The key for Triple DES must be 16 or 24 bytes (currently " + key.length + " bytes)");
|
||||
}
|
||||
return {
|
||||
"algorithm": "3DES-ECB",
|
||||
"key": key.length === 16 ? key + key.substring(0, 8) : key,
|
||||
"blockSize": 8,
|
||||
"Rb": new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0x1b]),
|
||||
};
|
||||
default:
|
||||
throw new OperationError("Undefined encryption algorithm");
|
||||
}
|
||||
})();
|
||||
|
||||
const xor = function(a, b, out) {
|
||||
if (!out) out = new Uint8Array(a.length);
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
out[i] = a[i] ^ b[i];
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
const leftShift1 = function(a) {
|
||||
const out = new Uint8Array(a.length);
|
||||
let carry = 0;
|
||||
for (let i = a.length - 1; i >= 0; i--) {
|
||||
out[i] = (a[i] << 1) | carry;
|
||||
carry = a[i] >> 7;
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
const cipher = forge.cipher.createCipher(info.algorithm, info.key);
|
||||
const encrypt = function(a, out) {
|
||||
if (!out) out = new Uint8Array(a.length);
|
||||
cipher.start();
|
||||
cipher.update(forge.util.createBuffer(a));
|
||||
cipher.finish();
|
||||
const cipherText = cipher.output.getBytes();
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
out[i] = cipherText.charCodeAt(i);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
const L = encrypt(new Uint8Array(info.blockSize));
|
||||
const K1 = leftShift1(L);
|
||||
if (L[0] & 0x80) xor(K1, info.Rb, K1);
|
||||
const K2 = leftShift1(K1);
|
||||
if (K1[0] & 0x80) xor(K2, info.Rb, K2);
|
||||
|
||||
const n = Math.ceil(input.byteLength / info.blockSize);
|
||||
const lastBlock = (function() {
|
||||
if (n === 0) {
|
||||
const data = new Uint8Array(K2);
|
||||
data[0] ^= 0x80;
|
||||
return data;
|
||||
}
|
||||
const inputLast = new Uint8Array(input, info.blockSize * (n - 1));
|
||||
if (inputLast.length === info.blockSize) {
|
||||
return xor(inputLast, K1, inputLast);
|
||||
} else {
|
||||
const data = new Uint8Array(info.blockSize);
|
||||
data.set(inputLast, 0);
|
||||
data[inputLast.length] = 0x80;
|
||||
return xor(data, K2, data);
|
||||
}
|
||||
})();
|
||||
|
||||
const X = new Uint8Array(info.blockSize);
|
||||
const Y = new Uint8Array(info.blockSize);
|
||||
for (let i = 0; i < n - 1; i++) {
|
||||
xor(X, new Uint8Array(input, info.blockSize * i, info.blockSize), Y);
|
||||
encrypt(Y, X);
|
||||
}
|
||||
xor(lastBlock, X, Y);
|
||||
const T = encrypt(Y);
|
||||
return toHexFast(T);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CMAC;
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import xmldom from "xmldom";
|
||||
import xmldom from "@xmldom/xmldom";
|
||||
import nwmatcher from "nwmatcher";
|
||||
|
||||
/**
|
||||
|
|
|
@ -21,7 +21,7 @@ class CTPH extends Operation {
|
|||
this.name = "CTPH";
|
||||
this.module = "Crypto";
|
||||
this.description = "Context Triggered Piecewise Hashing, also called Fuzzy Hashing, can match inputs that have homologies. Such inputs have sequences of identical bytes in the same order, although bytes in between these sequences may be different in both content and length.<br><br>CTPH was originally based on the work of Dr. Andrew Tridgell and a spam email detector called SpamSum. This method was adapted by Jesse Kornblum and published at the DFRWS conference in 2006 in a paper 'Identifying Almost Identical Files Using Context Triggered Piecewise Hashing'.";
|
||||
this.infoURL = "https://forensicswiki.xyz/wiki/index.php?title=Context_Triggered_Piecewise_Hashing";
|
||||
this.infoURL = "https://forensics.wiki/context_triggered_piecewise_hashing/";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
|
|
61
src/core/operations/CaesarBoxCipher.mjs
Normal file
61
src/core/operations/CaesarBoxCipher.mjs
Normal file
|
@ -0,0 +1,61 @@
|
|||
/**
|
||||
* @author n1073645 [n1073645@gmail.com]
|
||||
* @copyright Crown Copyright 2020
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
|
||||
/**
|
||||
* Caesar Box Cipher operation
|
||||
*/
|
||||
class CaesarBoxCipher extends Operation {
|
||||
|
||||
/**
|
||||
* CaesarBoxCipher constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Caesar Box Cipher";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Caesar Box is a transposition cipher used in the Roman Empire, in which letters of the message are written in rows in a square (or a rectangle) and then, read by column.";
|
||||
this.infoURL = "https://www.dcode.fr/caesar-box-cipher";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Box Height",
|
||||
type: "number",
|
||||
value: 1
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const tableHeight = args[0];
|
||||
const tableWidth = Math.ceil(input.length / tableHeight);
|
||||
while (input.indexOf(" ") !== -1)
|
||||
input = input.replace(" ", "");
|
||||
for (let i = 0; i < (tableHeight * tableWidth) - input.length; i++) {
|
||||
input += "\x00";
|
||||
}
|
||||
let result = "";
|
||||
for (let i = 0; i < tableHeight; i++) {
|
||||
for (let j = i; j < input.length; j += tableHeight) {
|
||||
if (input.charAt(j) !== "\x00") {
|
||||
result += input.charAt(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CaesarBoxCipher;
|
98
src/core/operations/CaretMdecode.mjs
Normal file
98
src/core/operations/CaretMdecode.mjs
Normal file
|
@ -0,0 +1,98 @@
|
|||
/**
|
||||
* @author tedk [tedk@ted.do]
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
|
||||
/**
|
||||
* Caret/M-decode operation
|
||||
*
|
||||
* https://gist.githubusercontent.com/JaHIY/3c91bbf7bea5661e6abfbd1349ee81a2/raw/c7b480e9ff24bcb8f5287a8a8a2dcb9bf5628506/decode_m_notation.cpp
|
||||
*/
|
||||
class CaretMdecode extends Operation {
|
||||
|
||||
/**
|
||||
* CaretMdecode constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Caret/M-decode";
|
||||
this.module = "Default";
|
||||
this.description = "Decodes caret or M-encoded strings, i.e. ^M turns into a newline, M-^] turns into 0x9d. Sources such as `cat -v`.\n\nPlease be aware that when using `cat -v` ^_ (caret-underscore) will not be encoded, but represents a valid encoding (namely that of 0x1f).";
|
||||
this.infoURL = "https://en.wikipedia.org/wiki/Caret_notation";
|
||||
this.inputType = "string";
|
||||
this.outputType = "byteArray";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
run(input, args) {
|
||||
|
||||
const bytes = [];
|
||||
|
||||
let prev = "";
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
|
||||
const charCode = input.charCodeAt(i);
|
||||
const curChar = input.charAt(i);
|
||||
|
||||
if (prev === "M-^") {
|
||||
if (charCode > 63 && charCode <= 95) {
|
||||
bytes.push(charCode + 64);
|
||||
} else if (charCode === 63) {
|
||||
bytes.push(255);
|
||||
} else {
|
||||
bytes.push(77, 45, 94, charCode);
|
||||
}
|
||||
prev = "";
|
||||
} else if (prev === "M-") {
|
||||
if (curChar === "^") {
|
||||
prev = prev + "^";
|
||||
} else if (charCode >= 32 && charCode <= 126) {
|
||||
bytes.push(charCode + 128);
|
||||
prev = "";
|
||||
} else {
|
||||
bytes.push(77, 45, charCode);
|
||||
prev = "";
|
||||
}
|
||||
} else if (prev === "M") {
|
||||
if (curChar === "-") {
|
||||
prev = prev + "-";
|
||||
} else {
|
||||
bytes.push(77, charCode);
|
||||
prev = "";
|
||||
}
|
||||
} else if (prev === "^") {
|
||||
if (charCode > 63 && charCode <= 126) {
|
||||
bytes.push(charCode - 64);
|
||||
} else if (charCode === 63) {
|
||||
bytes.push(127);
|
||||
} else {
|
||||
bytes.push(94, charCode);
|
||||
}
|
||||
prev = "";
|
||||
} else {
|
||||
if (curChar === "M") {
|
||||
prev = "M";
|
||||
} else if (curChar === "^") {
|
||||
prev = "^";
|
||||
} else {
|
||||
bytes.push(charCode);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CaretMdecode;
|
63
src/core/operations/CetaceanCipherDecode.mjs
Normal file
63
src/core/operations/CetaceanCipherDecode.mjs
Normal file
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* @author dolphinOnKeys [robin@weird.io]
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
|
||||
/**
|
||||
* Cetacean Cipher Decode operation
|
||||
*/
|
||||
class CetaceanCipherDecode extends Operation {
|
||||
|
||||
/**
|
||||
* CetaceanCipherDecode constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Cetacean Cipher Decode";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Decode Cetacean Cipher input. <br/><br/>e.g. <code>EEEEEEEEEeeEeEEEEEEEEEEEEeeEeEEe</code> becomes <code>hi</code>";
|
||||
this.infoURL = "https://hitchhikers.fandom.com/wiki/Dolphins";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
|
||||
this.checks = [
|
||||
{
|
||||
pattern: "^(?:[eE]{16,})(?: [eE]{16,})*$",
|
||||
flags: "",
|
||||
args: []
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const binaryArray = [];
|
||||
for (const char of input) {
|
||||
if (char === " ") {
|
||||
binaryArray.push(...[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]);
|
||||
} else {
|
||||
binaryArray.push(char === "e" ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
const byteArray = [];
|
||||
|
||||
for (let i = 0; i < binaryArray.length; i += 16) {
|
||||
byteArray.push(binaryArray.slice(i, i + 16).join(""));
|
||||
}
|
||||
|
||||
return byteArray.map(byte =>
|
||||
String.fromCharCode(parseInt(byte, 2))
|
||||
).join("");
|
||||
}
|
||||
}
|
||||
|
||||
export default CetaceanCipherDecode;
|
51
src/core/operations/CetaceanCipherEncode.mjs
Normal file
51
src/core/operations/CetaceanCipherEncode.mjs
Normal file
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* @author dolphinOnKeys [robin@weird.io]
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import {toBinary} from "../lib/Binary.mjs";
|
||||
|
||||
/**
|
||||
* Cetacean Cipher Encode operation
|
||||
*/
|
||||
class CetaceanCipherEncode extends Operation {
|
||||
|
||||
/**
|
||||
* CetaceanCipherEncode constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Cetacean Cipher Encode";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Converts any input into Cetacean Cipher. <br/><br/>e.g. <code>hi</code> becomes <code>EEEEEEEEEeeEeEEEEEEEEEEEEeeEeEEe</code>";
|
||||
this.infoURL = "https://hitchhikers.fandom.com/wiki/Dolphins";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const result = [];
|
||||
const charArray = input.split("");
|
||||
|
||||
charArray.map(character => {
|
||||
if (character === " ") {
|
||||
result.push(character);
|
||||
} else {
|
||||
const binaryArray = toBinary(character.charCodeAt(0), "None", 16).split("");
|
||||
result.push(binaryArray.map(str => str === "1" ? "e" : "E").join(""));
|
||||
}
|
||||
});
|
||||
|
||||
return result.join("");
|
||||
}
|
||||
}
|
||||
|
||||
export default CetaceanCipherEncode;
|
234
src/core/operations/ChaCha.mjs
Normal file
234
src/core/operations/ChaCha.mjs
Normal file
|
@ -0,0 +1,234 @@
|
|||
/**
|
||||
* @author joostrijneveld [joost@joostrijneveld.nl]
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import { toHex } from "../lib/Hex.mjs";
|
||||
|
||||
/**
|
||||
* Computes the ChaCha block function
|
||||
*
|
||||
* @param {byteArray} key
|
||||
* @param {byteArray} nonce
|
||||
* @param {byteArray} counter
|
||||
* @param {integer} rounds
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
function chacha(key, nonce, counter, rounds) {
|
||||
const tau = "expand 16-byte k";
|
||||
const sigma = "expand 32-byte k";
|
||||
|
||||
let state, c;
|
||||
if (key.length === 16) {
|
||||
c = Utils.strToByteArray(tau);
|
||||
state = c.concat(key).concat(key);
|
||||
} else {
|
||||
c = Utils.strToByteArray(sigma);
|
||||
state = c.concat(key);
|
||||
}
|
||||
state = state.concat(counter).concat(nonce);
|
||||
|
||||
const x = Array();
|
||||
for (let i = 0; i < 64; i += 4) {
|
||||
x.push(Utils.byteArrayToInt(state.slice(i, i + 4), "little"));
|
||||
}
|
||||
const a = [...x];
|
||||
|
||||
/**
|
||||
* Macro to compute a 32-bit rotate-left operation
|
||||
*
|
||||
* @param {integer} x
|
||||
* @param {integer} n
|
||||
* @returns {integer}
|
||||
*/
|
||||
function ROL32(x, n) {
|
||||
return ((x << n) & 0xFFFFFFFF) | (x >>> (32 - n));
|
||||
}
|
||||
|
||||
/**
|
||||
* Macro to compute a single ChaCha quarterround operation
|
||||
*
|
||||
* @param {integer} x
|
||||
* @param {integer} a
|
||||
* @param {integer} b
|
||||
* @param {integer} c
|
||||
* @param {integer} d
|
||||
* @returns {integer}
|
||||
*/
|
||||
function quarterround(x, a, b, c, d) {
|
||||
x[a] = ((x[a] + x[b]) & 0xFFFFFFFF); x[d] = ROL32(x[d] ^ x[a], 16);
|
||||
x[c] = ((x[c] + x[d]) & 0xFFFFFFFF); x[b] = ROL32(x[b] ^ x[c], 12);
|
||||
x[a] = ((x[a] + x[b]) & 0xFFFFFFFF); x[d] = ROL32(x[d] ^ x[a], 8);
|
||||
x[c] = ((x[c] + x[d]) & 0xFFFFFFFF); x[b] = ROL32(x[b] ^ x[c], 7);
|
||||
}
|
||||
|
||||
for (let i = 0; i < rounds / 2; i++) {
|
||||
quarterround(x, 0, 4, 8, 12);
|
||||
quarterround(x, 1, 5, 9, 13);
|
||||
quarterround(x, 2, 6, 10, 14);
|
||||
quarterround(x, 3, 7, 11, 15);
|
||||
quarterround(x, 0, 5, 10, 15);
|
||||
quarterround(x, 1, 6, 11, 12);
|
||||
quarterround(x, 2, 7, 8, 13);
|
||||
quarterround(x, 3, 4, 9, 14);
|
||||
}
|
||||
|
||||
for (let i = 0; i < 16; i++) {
|
||||
x[i] = (x[i] + a[i]) & 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
let output = Array();
|
||||
for (let i = 0; i < 16; i++) {
|
||||
output = output.concat(Utils.intToByteArray(x[i], 4, "little"));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* ChaCha operation
|
||||
*/
|
||||
class ChaCha extends Operation {
|
||||
|
||||
/**
|
||||
* ChaCha constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "ChaCha";
|
||||
this.module = "Ciphers";
|
||||
this.description = "ChaCha is a stream cipher designed by Daniel J. Bernstein. It is a variant of the Salsa stream cipher. Several parameterizations exist; 'ChaCha' may refer to the original construction, or to the variant as described in RFC-8439. ChaCha is often used with Poly1305, in the ChaCha20-Poly1305 AEAD construction.<br><br><b>Key:</b> ChaCha uses a key of 16 or 32 bytes (128 or 256 bits).<br><br><b>Nonce:</b> ChaCha uses a nonce of 8 or 12 bytes (64 or 96 bits).<br><br><b>Counter:</b> ChaCha uses a counter of 4 or 8 bytes (32 or 64 bits); together, the nonce and counter must add up to 16 bytes. The counter starts at zero at the start of the keystream, and is incremented at every 64 bytes.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Salsa20#ChaCha_variant";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Key",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
"name": "Nonce",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64", "Integer"]
|
||||
},
|
||||
{
|
||||
"name": "Counter",
|
||||
"type": "number",
|
||||
"value": 0,
|
||||
"min": 0
|
||||
},
|
||||
{
|
||||
"name": "Rounds",
|
||||
"type": "option",
|
||||
"value": ["20", "12", "8"]
|
||||
},
|
||||
{
|
||||
"name": "Input",
|
||||
"type": "option",
|
||||
"value": ["Hex", "Raw"]
|
||||
},
|
||||
{
|
||||
"name": "Output",
|
||||
"type": "option",
|
||||
"value": ["Raw", "Hex"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const key = Utils.convertToByteArray(args[0].string, args[0].option),
|
||||
nonceType = args[1].option,
|
||||
rounds = parseInt(args[3], 10),
|
||||
inputType = args[4],
|
||||
outputType = args[5];
|
||||
|
||||
if (key.length !== 16 && key.length !== 32) {
|
||||
throw new OperationError(`Invalid key length: ${key.length} bytes.
|
||||
|
||||
ChaCha uses a key of 16 or 32 bytes (128 or 256 bits).`);
|
||||
}
|
||||
|
||||
let counter, nonce, counterLength;
|
||||
if (nonceType === "Integer") {
|
||||
nonce = Utils.intToByteArray(parseInt(args[1].string, 10), 12, "little");
|
||||
counterLength = 4;
|
||||
} else {
|
||||
nonce = Utils.convertToByteArray(args[1].string, args[1].option);
|
||||
if (!(nonce.length === 12 || nonce.length === 8)) {
|
||||
throw new OperationError(`Invalid nonce length: ${nonce.length} bytes.
|
||||
|
||||
ChaCha uses a nonce of 8 or 12 bytes (64 or 96 bits).`);
|
||||
}
|
||||
counterLength = 16 - nonce.length;
|
||||
}
|
||||
counter = Utils.intToByteArray(args[2], counterLength, "little");
|
||||
|
||||
const output = [];
|
||||
input = Utils.convertToByteArray(input, inputType);
|
||||
|
||||
let counterAsInt = Utils.byteArrayToInt(counter, "little");
|
||||
for (let i = 0; i < input.length; i += 64) {
|
||||
counter = Utils.intToByteArray(counterAsInt, counterLength, "little");
|
||||
const stream = chacha(key, nonce, counter, rounds);
|
||||
for (let j = 0; j < 64 && i + j < input.length; j++) {
|
||||
output.push(input[i + j] ^ stream[j]);
|
||||
}
|
||||
counterAsInt++;
|
||||
}
|
||||
|
||||
if (outputType === "Hex") {
|
||||
return toHex(output);
|
||||
} else {
|
||||
return Utils.arrayBufferToStr(Uint8Array.from(output).buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight ChaCha
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlight(pos, args) {
|
||||
const inputType = args[4],
|
||||
outputType = args[5];
|
||||
if (inputType === "Raw" && outputType === "Raw") {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight ChaCha in reverse
|
||||
*
|
||||
* @param {Object[]} pos
|
||||
* @param {number} pos[].start
|
||||
* @param {number} pos[].end
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} pos
|
||||
*/
|
||||
highlightReverse(pos, args) {
|
||||
const inputType = args[4],
|
||||
outputType = args[5];
|
||||
if (inputType === "Raw" && outputType === "Raw") {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ChaCha;
|
|
@ -1,6 +1,9 @@
|
|||
/**
|
||||
* Emulation of Colossus.
|
||||
*
|
||||
* Tested against the Colossus Rebuild at Bletchley Park's TNMOC
|
||||
* using a variety of inputs and settings to confirm correctness.
|
||||
*
|
||||
* @author VirtualColossus [martin@virtualcolossus.co.uk]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
|
|
|
@ -24,7 +24,7 @@ class CompareCTPHHashes extends Operation {
|
|||
this.name = "Compare CTPH hashes";
|
||||
this.module = "Crypto";
|
||||
this.description = "Compares two Context Triggered Piecewise Hashing (CTPH) fuzzy hashes to determine the similarity between them on a scale of 0 to 100.";
|
||||
this.infoURL = "https://forensicswiki.xyz/wiki/index.php?title=Context_Triggered_Piecewise_Hashing";
|
||||
this.infoURL = "https://forensics.wiki/context_triggered_piecewise_hashing/";
|
||||
this.inputType = "string";
|
||||
this.outputType = "Number";
|
||||
this.args = [
|
||||
|
|
|
@ -24,7 +24,7 @@ class CompareSSDEEPHashes extends Operation {
|
|||
this.name = "Compare SSDEEP hashes";
|
||||
this.module = "Crypto";
|
||||
this.description = "Compares two SSDEEP fuzzy hashes to determine the similarity between them on a scale of 0 to 100.";
|
||||
this.infoURL = "https://forensicswiki.xyz/wiki/index.php?title=Ssdeep";
|
||||
this.infoURL = "https://forensics.wiki/ssdeep/";
|
||||
this.inputType = "string";
|
||||
this.outputType = "Number";
|
||||
this.args = [
|
||||
|
|
|
@ -64,6 +64,7 @@ class ConditionalJump extends Operation {
|
|||
jmpIndex = getLabelIndex(label, state);
|
||||
|
||||
if (state.numJumps >= maxJumps || jmpIndex === -1) {
|
||||
state.numJumps = 0;
|
||||
return state;
|
||||
}
|
||||
|
||||
|
@ -73,6 +74,8 @@ class ConditionalJump extends Operation {
|
|||
if (!invert && strMatch || invert && !strMatch) {
|
||||
state.progress = jmpIndex;
|
||||
state.numJumps++;
|
||||
} else {
|
||||
state.numJumps = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,8 +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 jimplib from "jimp/es/index.js";
|
||||
const jimp = jimplib.default ? jimplib.default : jimplib;
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Contain Image operation
|
||||
|
@ -92,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)) {
|
||||
|
@ -114,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})`);
|
||||
}
|
||||
|
@ -124,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,8 +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 jimplib from "jimp/es/index.js";
|
||||
const jimp = jimplib.default ? jimplib.default : jimplib;
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Convert Image Format operation
|
||||
|
@ -77,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];
|
||||
|
@ -99,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})`);
|
||||
}
|
||||
|
|
|
@ -9,8 +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 jimplib from "jimp/es/index.js";
|
||||
const jimp = jimplib.default ? jimplib.default : jimplib;
|
||||
import jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Cover Image operation
|
||||
|
|
|
@ -9,8 +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 jimplib from "jimp/es/index.js";
|
||||
const jimp = jimplib.default ? jimplib.default : jimplib;
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Crop Image operation
|
||||
|
@ -100,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})`);
|
||||
}
|
||||
|
@ -120,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.";
|
||||
this.description = "DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.<br><br><b>Key:</b> DES uses a key length of 8 bytes (64 bits).<br>Triple DES uses a key length of 24 bytes (192 bits).<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used as a default.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Data_Encryption_Standard";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
|
@ -42,7 +42,7 @@ class DESDecrypt extends Operation {
|
|||
{
|
||||
"name": "Mode",
|
||||
"type": "option",
|
||||
"value": ["CBC", "CFB", "OFB", "CTR", "ECB"]
|
||||
"value": ["CBC", "CFB", "OFB", "CTR", "ECB", "CBC/NoPadding", "ECB/NoPadding"]
|
||||
},
|
||||
{
|
||||
"name": "Input",
|
||||
|
@ -65,7 +65,9 @@ class DESDecrypt extends Operation {
|
|||
run(input, args) {
|
||||
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
||||
iv = Utils.convertToByteArray(args[1].string, args[1].option),
|
||||
[,, mode, inputType, outputType] = args;
|
||||
mode = args[2].substring(0, 3),
|
||||
noPadding = args[2].endsWith("NoPadding"),
|
||||
[,,, inputType, outputType] = args;
|
||||
|
||||
if (key.length !== 8) {
|
||||
throw new OperationError(`Invalid key length: ${key.length} bytes
|
||||
|
@ -83,6 +85,14 @@ Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
|
|||
input = Utils.convertToByteString(input, inputType);
|
||||
|
||||
const decipher = forge.cipher.createDecipher("DES-" + mode, key);
|
||||
|
||||
/* Allow for a "no padding" mode */
|
||||
if (noPadding) {
|
||||
decipher.mode.unpad = function(output, options) {
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
decipher.start({iv: iv});
|
||||
decipher.update(forge.util.createBuffer(input));
|
||||
const result = decipher.finish();
|
||||
|
|
|
@ -51,10 +51,27 @@ class DNSOverHTTPS extends Operation {
|
|||
value: [
|
||||
"A",
|
||||
"AAAA",
|
||||
"TXT",
|
||||
"MX",
|
||||
"ANAME",
|
||||
"CERT",
|
||||
"CNAME",
|
||||
"DNSKEY",
|
||||
"NS"
|
||||
"HTTPS",
|
||||
"IPSECKEY",
|
||||
"LOC",
|
||||
"MX",
|
||||
"NS",
|
||||
"OPENPGPKEY",
|
||||
"PTR",
|
||||
"RRSIG",
|
||||
"SIG",
|
||||
"SOA",
|
||||
"SPF",
|
||||
"SRV",
|
||||
"SSHFP",
|
||||
"TA",
|
||||
"TXT",
|
||||
"URI",
|
||||
"ANY"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
107
src/core/operations/DateTimeDelta.mjs
Normal file
107
src/core/operations/DateTimeDelta.mjs
Normal file
|
@ -0,0 +1,107 @@
|
|||
/**
|
||||
* @author tomgond [tom.gonda@gmail.com]
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import moment from "moment-timezone";
|
||||
import {DATETIME_FORMATS, FORMAT_EXAMPLES} from "../lib/DateTime.mjs";
|
||||
|
||||
/**
|
||||
* DateTime Delta operation
|
||||
*/
|
||||
class DateTimeDelta extends Operation {
|
||||
|
||||
/**
|
||||
* DateTimeDelta constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "DateTime Delta";
|
||||
this.module = "Default";
|
||||
this.description = "Calculates a new DateTime value given an input DateTime value and a time difference (delta) from the input DateTime value.";
|
||||
this.inputType = "string";
|
||||
this.outputType = "html";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Built in formats",
|
||||
"type": "populateOption",
|
||||
"value": DATETIME_FORMATS,
|
||||
"target": 1
|
||||
},
|
||||
{
|
||||
"name": "Input format string",
|
||||
"type": "binaryString",
|
||||
"value": "DD/MM/YYYY HH:mm:ss"
|
||||
},
|
||||
{
|
||||
"name": "Time Operation",
|
||||
"type": "option",
|
||||
"value": ["Add", "Subtract"]
|
||||
},
|
||||
{
|
||||
"name": "Days",
|
||||
"type": "number",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"name": "Hours",
|
||||
"type": "number",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"name": "Minutes",
|
||||
"type": "number",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"name": "Seconds",
|
||||
"type": "number",
|
||||
"value": 0
|
||||
}
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const inputTimezone = "UTC";
|
||||
const inputFormat = args[1];
|
||||
const operationType = args[2];
|
||||
const daysDelta = args[3];
|
||||
const hoursDelta = args[4];
|
||||
const minutesDelta = args[5];
|
||||
const secondsDelta = args[6];
|
||||
let date = "";
|
||||
|
||||
try {
|
||||
date = moment.tz(input, inputFormat, inputTimezone);
|
||||
if (!date || date.format() === "Invalid date") throw Error;
|
||||
} catch (err) {
|
||||
return `Invalid format.\n\n${FORMAT_EXAMPLES}`;
|
||||
}
|
||||
let newDate;
|
||||
if (operationType === "Add") {
|
||||
newDate = date.add(daysDelta, "days")
|
||||
.add(hoursDelta, "hours")
|
||||
.add(minutesDelta, "minutes")
|
||||
.add(secondsDelta, "seconds");
|
||||
|
||||
} else {
|
||||
newDate = date.add(-daysDelta, "days")
|
||||
.add(-hoursDelta, "hours")
|
||||
.add(-minutesDelta, "minutes")
|
||||
.add(-secondsDelta, "seconds");
|
||||
}
|
||||
return newDate.tz(inputTimezone).format(inputFormat.replace(/[<>]/g, ""));
|
||||
}
|
||||
}
|
||||
|
||||
export default DateTimeDelta;
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import Operation from "../Operation.mjs";
|
||||
import cptable from "codepage";
|
||||
import {IO_FORMAT} from "../lib/ChrEnc.mjs";
|
||||
import {CHR_ENC_CODE_PAGES} from "../lib/ChrEnc.mjs";
|
||||
|
||||
/**
|
||||
* Decode text operation
|
||||
|
@ -26,7 +26,7 @@ class DecodeText extends Operation {
|
|||
"<br><br>",
|
||||
"Supported charsets are:",
|
||||
"<ul>",
|
||||
Object.keys(IO_FORMAT).map(e => `<li>${e}</li>`).join("\n"),
|
||||
Object.keys(CHR_ENC_CODE_PAGES).map(e => `<li>${e}</li>`).join("\n"),
|
||||
"</ul>",
|
||||
].join("\n");
|
||||
this.infoURL = "https://wikipedia.org/wiki/Character_encoding";
|
||||
|
@ -36,7 +36,7 @@ class DecodeText extends Operation {
|
|||
{
|
||||
"name": "Encoding",
|
||||
"type": "option",
|
||||
"value": Object.keys(IO_FORMAT)
|
||||
"value": Object.keys(CHR_ENC_CODE_PAGES)
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ class DecodeText extends Operation {
|
|||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const format = IO_FORMAT[args[0]];
|
||||
const format = CHR_ENC_CODE_PAGES[args[0]];
|
||||
return cptable.utils.decode(format, new Uint8Array(input));
|
||||
}
|
||||
|
||||
|
|
|
@ -62,12 +62,14 @@ class DeriveEVPKey extends Operation {
|
|||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const passphrase = Utils.convertToByteString(args[0].string, args[0].option),
|
||||
const passphrase = CryptoJS.enc.Latin1.parse(
|
||||
Utils.convertToByteString(args[0].string, args[0].option)),
|
||||
keySize = args[1] / 32,
|
||||
iterations = args[2],
|
||||
hasher = args[3],
|
||||
salt = Utils.convertToByteString(args[4].string, args[4].option),
|
||||
key = CryptoJS.EvpKDF(passphrase, salt, {
|
||||
salt = CryptoJS.enc.Latin1.parse(
|
||||
Utils.convertToByteString(args[4].string, args[4].option)),
|
||||
key = CryptoJS.EvpKDF(passphrase, salt, { // lgtm [js/insufficient-password-hash]
|
||||
keySize: keySize,
|
||||
hasher: CryptoJS.algo[hasher],
|
||||
iterations: iterations,
|
||||
|
|
138
src/core/operations/DeriveHKDFKey.mjs
Normal file
138
src/core/operations/DeriveHKDFKey.mjs
Normal file
|
@ -0,0 +1,138 @@
|
|||
/**
|
||||
* @author mikecat
|
||||
* @copyright Crown Copyright 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import CryptoApi from "crypto-api/src/crypto-api.mjs";
|
||||
|
||||
/**
|
||||
* Derive HKDF Key operation
|
||||
*/
|
||||
class DeriveHKDFKey extends Operation {
|
||||
|
||||
/**
|
||||
* DeriveHKDFKey constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Derive HKDF key";
|
||||
this.module = "Crypto";
|
||||
this.description = "A simple Hashed Message Authenticaton Code (HMAC)-based key derivation function (HKDF), defined in RFC5869.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/HKDF";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Salt",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "Decimal", "Base64", "UTF8", "Latin1"]
|
||||
},
|
||||
{
|
||||
"name": "Info",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["Hex", "Decimal", "Base64", "UTF8", "Latin1"]
|
||||
},
|
||||
{
|
||||
"name": "Hashing function",
|
||||
"type": "option",
|
||||
"value": [
|
||||
"MD2",
|
||||
"MD4",
|
||||
"MD5",
|
||||
"SHA0",
|
||||
"SHA1",
|
||||
"SHA224",
|
||||
"SHA256",
|
||||
"SHA384",
|
||||
"SHA512",
|
||||
"SHA512/224",
|
||||
"SHA512/256",
|
||||
"RIPEMD128",
|
||||
"RIPEMD160",
|
||||
"RIPEMD256",
|
||||
"RIPEMD320",
|
||||
"HAS160",
|
||||
"Whirlpool",
|
||||
"Whirlpool-0",
|
||||
"Whirlpool-T",
|
||||
"Snefru"
|
||||
],
|
||||
"defaultIndex": 6
|
||||
},
|
||||
{
|
||||
"name": "Extract mode",
|
||||
"type": "argSelector",
|
||||
"value": [
|
||||
{
|
||||
"name": "with salt",
|
||||
"on": [0]
|
||||
},
|
||||
{
|
||||
"name": "no salt",
|
||||
"off": [0]
|
||||
},
|
||||
{
|
||||
"name": "skip",
|
||||
"off": [0]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "L (number of output octets)",
|
||||
"type": "number",
|
||||
"value": 16,
|
||||
"min": 0
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
run(input, args) {
|
||||
const argSalt = Utils.convertToByteString(args[0].string || "", args[0].option),
|
||||
info = Utils.convertToByteString(args[1].string || "", args[1].option),
|
||||
hashFunc = args[2].toLowerCase(),
|
||||
extractMode = args[3],
|
||||
L = args[4],
|
||||
IKM = Utils.arrayBufferToStr(input, false),
|
||||
hasher = CryptoApi.getHasher(hashFunc),
|
||||
HashLen = hasher.finalize().length;
|
||||
|
||||
if (L < 0) {
|
||||
throw new OperationError("L must be non-negative");
|
||||
}
|
||||
if (L > 255 * HashLen) {
|
||||
throw new OperationError("L too large (maximum length for " + args[2] + " is " + (255 * HashLen) + ")");
|
||||
}
|
||||
|
||||
const hmacHash = function(key, data) {
|
||||
hasher.reset();
|
||||
const mac = CryptoApi.getHmac(key, hasher);
|
||||
mac.update(data);
|
||||
return mac.finalize();
|
||||
};
|
||||
const salt = extractMode === "with salt" ? argSalt : "\0".repeat(HashLen);
|
||||
const PRK = extractMode === "skip" ? IKM : hmacHash(salt, IKM);
|
||||
let T = "";
|
||||
let result = "";
|
||||
for (let i = 1; i <= 255 && result.length < L; i++) {
|
||||
const TNext = hmacHash(PRK, T + info + String.fromCharCode(i));
|
||||
result += TNext;
|
||||
T = TNext;
|
||||
}
|
||||
return CryptoApi.encoder.toHex(result.substring(0, L));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default DeriveHKDFKey;
|
|
@ -65,7 +65,7 @@ class DetectFileType extends Operation {
|
|||
Extension: ${type.extension}
|
||||
MIME type: ${type.mime}\n`;
|
||||
|
||||
if (type.description && type.description.length) {
|
||||
if (type?.description?.length) {
|
||||
output += `Description: ${type.description}\n`;
|
||||
}
|
||||
|
||||
|
|
|
@ -119,9 +119,9 @@ class Diff extends Operation {
|
|||
|
||||
for (let i = 0; i < diff.length; i++) {
|
||||
if (diff[i].added) {
|
||||
if (showAdded) output += "<span class='hl5'>" + Utils.escapeHtml(diff[i].value) + "</span>";
|
||||
if (showAdded) output += "<ins>" + Utils.escapeHtml(diff[i].value) + "</ins>";
|
||||
} else if (diff[i].removed) {
|
||||
if (showRemoved) output += "<span class='hl3'>" + Utils.escapeHtml(diff[i].value) + "</span>";
|
||||
if (showRemoved) output += "<del>" + Utils.escapeHtml(diff[i].value) + "</del>";
|
||||
} else if (!showSubtraction) {
|
||||
output += Utils.escapeHtml(diff[i].value);
|
||||
}
|
||||
|
|
|
@ -9,8 +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 jimplib from "jimp/es/index.js";
|
||||
const jimp = jimplib.default ? jimplib.default : jimplib;
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Image Dither operation
|
||||
|
@ -45,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})`);
|
||||
}
|
||||
|
@ -56,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) {
|
||||
|
|
107
src/core/operations/ECDSASign.mjs
Normal file
107
src/core/operations/ECDSASign.mjs
Normal file
|
@ -0,0 +1,107 @@
|
|||
/**
|
||||
* @author cplussharp
|
||||
* @copyright Crown Copyright 2021
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import { fromHex } from "../lib/Hex.mjs";
|
||||
import { toBase64 } from "../lib/Base64.mjs";
|
||||
import r from "jsrsasign";
|
||||
|
||||
/**
|
||||
* ECDSA Sign operation
|
||||
*/
|
||||
class ECDSASign extends Operation {
|
||||
|
||||
/**
|
||||
* ECDSASign constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "ECDSA Sign";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Sign a plaintext message with a PEM encoded EC key.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "ECDSA Private Key (PEM)",
|
||||
type: "text",
|
||||
value: "-----BEGIN EC PRIVATE KEY-----"
|
||||
},
|
||||
{
|
||||
name: "Message Digest Algorithm",
|
||||
type: "option",
|
||||
value: [
|
||||
"SHA-256",
|
||||
"SHA-384",
|
||||
"SHA-512",
|
||||
"SHA-1",
|
||||
"MD5"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Output Format",
|
||||
type: "option",
|
||||
value: [
|
||||
"ASN.1 HEX",
|
||||
"P1363 HEX",
|
||||
"JSON Web Signature",
|
||||
"Raw JSON"
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [keyPem, mdAlgo, outputFormat] = args;
|
||||
|
||||
if (keyPem.replace("-----BEGIN EC PRIVATE KEY-----", "").length === 0) {
|
||||
throw new OperationError("Please enter a private key.");
|
||||
}
|
||||
|
||||
const internalAlgorithmName = mdAlgo.replace("-", "") + "withECDSA";
|
||||
const sig = new r.KJUR.crypto.Signature({ alg: internalAlgorithmName });
|
||||
const key = r.KEYUTIL.getKey(keyPem);
|
||||
if (key.type !== "EC") {
|
||||
throw new OperationError("Provided key is not an EC key.");
|
||||
}
|
||||
if (!key.isPrivate) {
|
||||
throw new OperationError("Provided key is not a private key.");
|
||||
}
|
||||
sig.init(key);
|
||||
const signatureASN1Hex = sig.signString(input);
|
||||
|
||||
let result;
|
||||
switch (outputFormat) {
|
||||
case "ASN.1 HEX":
|
||||
result = signatureASN1Hex;
|
||||
break;
|
||||
case "P1363 HEX":
|
||||
result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex);
|
||||
break;
|
||||
case "JSON Web Signature":
|
||||
result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex);
|
||||
result = toBase64(fromHex(result), "A-Za-z0-9-_"); // base64url
|
||||
break;
|
||||
case "Raw JSON": {
|
||||
const signatureRS = r.KJUR.crypto.ECDSA.parseSigHexInHexRS(signatureASN1Hex);
|
||||
result = JSON.stringify(signatureRS);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export default ECDSASign;
|
146
src/core/operations/ECDSASignatureConversion.mjs
Normal file
146
src/core/operations/ECDSASignatureConversion.mjs
Normal file
|
@ -0,0 +1,146 @@
|
|||
/**
|
||||
* @author cplussharp
|
||||
* @copyright Crown Copyright 2021
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import { fromBase64, toBase64 } from "../lib/Base64.mjs";
|
||||
import { fromHex, toHexFast } from "../lib/Hex.mjs";
|
||||
import r from "jsrsasign";
|
||||
|
||||
/**
|
||||
* ECDSA Sign operation
|
||||
*/
|
||||
class ECDSASignatureConversion extends Operation {
|
||||
|
||||
/**
|
||||
* ECDSASignatureConversion constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "ECDSA Signature Conversion";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Convert an ECDSA signature between hex, asn1 and json.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Input Format",
|
||||
type: "option",
|
||||
value: [
|
||||
"Auto",
|
||||
"ASN.1 HEX",
|
||||
"P1363 HEX",
|
||||
"JSON Web Signature",
|
||||
"Raw JSON"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Output Format",
|
||||
type: "option",
|
||||
value: [
|
||||
"ASN.1 HEX",
|
||||
"P1363 HEX",
|
||||
"JSON Web Signature",
|
||||
"Raw JSON"
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
let inputFormat = args[0];
|
||||
const outputFormat = args[1];
|
||||
|
||||
// detect input format
|
||||
let inputJson;
|
||||
if (inputFormat === "Auto") {
|
||||
try {
|
||||
inputJson = JSON.parse(input);
|
||||
if (typeof(inputJson) === "object") {
|
||||
inputFormat = "Raw JSON";
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if (inputFormat === "Auto") {
|
||||
const hexRegex = /^[a-f\d]{2,}$/gi;
|
||||
if (hexRegex.test(input)) {
|
||||
if (input.substring(0, 2) === "30" && r.ASN1HEX.isASN1HEX(input)) {
|
||||
inputFormat = "ASN.1 HEX";
|
||||
} else {
|
||||
inputFormat = "P1363 HEX";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let inputBase64;
|
||||
if (inputFormat === "Auto") {
|
||||
try {
|
||||
inputBase64 = fromBase64(input, "A-Za-z0-9-_", false);
|
||||
inputFormat = "JSON Web Signature";
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// convert input to ASN.1 hex
|
||||
let signatureASN1Hex;
|
||||
switch (inputFormat) {
|
||||
case "Auto":
|
||||
throw new OperationError("Signature format could not be detected");
|
||||
case "ASN.1 HEX":
|
||||
signatureASN1Hex = input;
|
||||
break;
|
||||
case "P1363 HEX":
|
||||
signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(input);
|
||||
break;
|
||||
case "JSON Web Signature":
|
||||
if (!inputBase64) inputBase64 = fromBase64(input, "A-Za-z0-9-_");
|
||||
signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(toHexFast(inputBase64));
|
||||
break;
|
||||
case "Raw JSON": {
|
||||
if (!inputJson) inputJson = JSON.parse(input);
|
||||
if (!inputJson.r) {
|
||||
throw new OperationError('No "r" value in the signature JSON');
|
||||
}
|
||||
if (!inputJson.s) {
|
||||
throw new OperationError('No "s" value in the signature JSON');
|
||||
}
|
||||
signatureASN1Hex = r.KJUR.crypto.ECDSA.hexRSSigToASN1Sig(inputJson.r, inputJson.s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// convert ASN.1 hex to output format
|
||||
let result;
|
||||
switch (outputFormat) {
|
||||
case "ASN.1 HEX":
|
||||
result = signatureASN1Hex;
|
||||
break;
|
||||
case "P1363 HEX":
|
||||
result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex);
|
||||
break;
|
||||
case "JSON Web Signature":
|
||||
result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex);
|
||||
result = toBase64(fromHex(result), "A-Za-z0-9-_"); // base64url
|
||||
break;
|
||||
case "Raw JSON": {
|
||||
const signatureRS = r.KJUR.crypto.ECDSA.parseSigHexInHexRS(signatureASN1Hex);
|
||||
result = JSON.stringify(signatureRS);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export default ECDSASignatureConversion;
|
154
src/core/operations/ECDSAVerify.mjs
Normal file
154
src/core/operations/ECDSAVerify.mjs
Normal file
|
@ -0,0 +1,154 @@
|
|||
/**
|
||||
* @author cplussharp
|
||||
* @copyright Crown Copyright 2021
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import { fromBase64 } from "../lib/Base64.mjs";
|
||||
import { toHexFast } from "../lib/Hex.mjs";
|
||||
import r from "jsrsasign";
|
||||
|
||||
/**
|
||||
* ECDSA Verify operation
|
||||
*/
|
||||
class ECDSAVerify extends Operation {
|
||||
|
||||
/**
|
||||
* ECDSAVerify constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "ECDSA Verify";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Verify a message against a signature and a public PEM encoded EC key.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Input Format",
|
||||
type: "option",
|
||||
value: [
|
||||
"Auto",
|
||||
"ASN.1 HEX",
|
||||
"P1363 HEX",
|
||||
"JSON Web Signature",
|
||||
"Raw JSON"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Message Digest Algorithm",
|
||||
type: "option",
|
||||
value: [
|
||||
"SHA-256",
|
||||
"SHA-384",
|
||||
"SHA-512",
|
||||
"SHA-1",
|
||||
"MD5"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "ECDSA Public Key (PEM)",
|
||||
type: "text",
|
||||
value: "-----BEGIN PUBLIC KEY-----"
|
||||
},
|
||||
{
|
||||
name: "Message",
|
||||
type: "text",
|
||||
value: ""
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
let inputFormat = args[0];
|
||||
const [, mdAlgo, keyPem, msg] = args;
|
||||
|
||||
if (keyPem.replace("-----BEGIN PUBLIC KEY-----", "").length === 0) {
|
||||
throw new OperationError("Please enter a public key.");
|
||||
}
|
||||
|
||||
// detect input format
|
||||
let inputJson;
|
||||
if (inputFormat === "Auto") {
|
||||
try {
|
||||
inputJson = JSON.parse(input);
|
||||
if (typeof(inputJson) === "object") {
|
||||
inputFormat = "Raw JSON";
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if (inputFormat === "Auto") {
|
||||
const hexRegex = /^[a-f\d]{2,}$/gi;
|
||||
if (hexRegex.test(input)) {
|
||||
if (input.substring(0, 2) === "30" && r.ASN1HEX.isASN1HEX(input)) {
|
||||
inputFormat = "ASN.1 HEX";
|
||||
} else {
|
||||
inputFormat = "P1363 HEX";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let inputBase64;
|
||||
if (inputFormat === "Auto") {
|
||||
try {
|
||||
inputBase64 = fromBase64(input, "A-Za-z0-9-_", false);
|
||||
inputFormat = "JSON Web Signature";
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// convert to ASN.1 signature
|
||||
let signatureASN1Hex;
|
||||
switch (inputFormat) {
|
||||
case "Auto":
|
||||
throw new OperationError("Signature format could not be detected");
|
||||
case "ASN.1 HEX":
|
||||
signatureASN1Hex = input;
|
||||
break;
|
||||
case "P1363 HEX":
|
||||
signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(input);
|
||||
break;
|
||||
case "JSON Web Signature":
|
||||
if (!inputBase64) inputBase64 = fromBase64(input, "A-Za-z0-9-_");
|
||||
signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(toHexFast(inputBase64));
|
||||
break;
|
||||
case "Raw JSON": {
|
||||
if (!inputJson) inputJson = JSON.parse(input);
|
||||
if (!inputJson.r) {
|
||||
throw new OperationError('No "r" value in the signature JSON');
|
||||
}
|
||||
if (!inputJson.s) {
|
||||
throw new OperationError('No "s" value in the signature JSON');
|
||||
}
|
||||
signatureASN1Hex = r.KJUR.crypto.ECDSA.hexRSSigToASN1Sig(inputJson.r, inputJson.s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// verify signature
|
||||
const internalAlgorithmName = mdAlgo.replace("-", "") + "withECDSA";
|
||||
const sig = new r.KJUR.crypto.Signature({ alg: internalAlgorithmName });
|
||||
const key = r.KEYUTIL.getKey(keyPem);
|
||||
if (key.type !== "EC") {
|
||||
throw new OperationError("Provided key is not an EC key.");
|
||||
}
|
||||
if (!key.isPublic) {
|
||||
throw new OperationError("Provided key is not a public key.");
|
||||
}
|
||||
sig.init(key);
|
||||
sig.updateString(msg);
|
||||
const result = sig.verify(signatureASN1Hex);
|
||||
return result ? "Verified OK" : "Verification Failure";
|
||||
}
|
||||
}
|
||||
|
||||
export default ECDSAVerify;
|
913
src/core/operations/ELFInfo.mjs
Normal file
913
src/core/operations/ELFInfo.mjs
Normal file
|
@ -0,0 +1,913 @@
|
|||
/**
|
||||
* @author n1073645 [n1073645@gmail.com]
|
||||
* @copyright Crown Copyright 2022
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Stream from "../lib/Stream.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* ELF Info operation
|
||||
*/
|
||||
class ELFInfo extends Operation {
|
||||
|
||||
/**
|
||||
* ELFInfo constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "ELF Info";
|
||||
this.module = "Default";
|
||||
this.description = "Implements readelf-like functionality. This operation will extract the ELF Header, Program Headers, Section Headers and Symbol Table for an ELF file.";
|
||||
this.infoURL = "https://www.wikipedia.org/wiki/Executable_and_Linkable_Format";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
let phoff = 0;
|
||||
let phEntries = 0;
|
||||
let shoff = 0;
|
||||
let shEntries = 0;
|
||||
let shentSize = 0;
|
||||
let entry = 0;
|
||||
let format = 0;
|
||||
let endianness = "";
|
||||
let shstrtab = 0;
|
||||
|
||||
let namesOffset = 0;
|
||||
|
||||
let symtabOffset = 0;
|
||||
let symtabSize = 0;
|
||||
let symtabEntSize = 0;
|
||||
|
||||
let strtabOffset = 0;
|
||||
const align = 30;
|
||||
|
||||
/**
|
||||
* This function reads characters until it hits a null terminator.
|
||||
*
|
||||
* @param {stream} stream
|
||||
* @param {integer} namesOffset
|
||||
* @param {integer} nameOffset
|
||||
* @returns {string}
|
||||
*/
|
||||
function readString(stream, namesOffset, nameOffset) {
|
||||
const preMove = stream.position;
|
||||
stream.moveTo(namesOffset + nameOffset);
|
||||
|
||||
const nameResult = stream.readString();
|
||||
stream.moveTo(preMove);
|
||||
return nameResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function parses and extracts relevant information from the ELF Header.
|
||||
*
|
||||
* @param {stream} stream
|
||||
* @returns {string}
|
||||
*/
|
||||
function elfHeader(stream) {
|
||||
/**
|
||||
* The ELF Header is comprised of the following structures depending on the binary's format.
|
||||
*
|
||||
* e_ident - The Magic Number 0x7F,0x45,0x4c,0x46
|
||||
* - Byte set to 1 or 2 to signify 32-bit or 64-bit format, respectively.
|
||||
* - Byte set to 1 or 2 to signify little of big endianness, respectively.
|
||||
* - Byte set to 1 for the version of ELF.
|
||||
* - Byte identifying the target OS ABI.
|
||||
* - Byte further identifying the OS ABI Version.
|
||||
* - 7 Padding Bytes.
|
||||
* e_type - 2 bytes identifying the object file type.
|
||||
* e_machine - 2 bytes identifying the instruction set architecture.
|
||||
* e_version - Byte set to 1 for the version of ELF.
|
||||
*
|
||||
* 32-bit:
|
||||
* e_entry - 4 Bytes specifying the entry point.
|
||||
* e_phoff - 4 Bytes specifying the offset of the Program Header Table.
|
||||
* e_shoff - 4 Bytes specifying the offset of the Section Header Table.
|
||||
*
|
||||
* 64-bit:
|
||||
* e_entry - 8 Bytes specifying the entry point.
|
||||
* e_phoff - 8 Bytes specifying the offset of the Program Header Table.
|
||||
* e_shoff - 8 Bytes specifying the offset of the Section Header Table.
|
||||
*
|
||||
* e_flags - 4 Bytes specifying processor specific flags.
|
||||
* e_ehsize - 2 Bytes specifying the size of the ELF Header.
|
||||
* e_phentsize - 2 Bytes specifying the size of a Program Header Table Entry.
|
||||
* e_phnum - 2 Bytes specifying the number of entries in the Program Header Table.
|
||||
* e_shentsize - 2 Bytes specifying the size of a Section Header Table Entry.
|
||||
* e_shnum - 2 Bytes specifying the number of entries in the Section Header Table.
|
||||
* e_shstrndx - 2 Bytes specifying the index of the section containing the section names in the Section Header Table.
|
||||
*/
|
||||
const ehResult = [];
|
||||
|
||||
const magic = stream.getBytes(4);
|
||||
if (magic.join("") !== [0x7f, 0x45, 0x4c, 0x46].join(""))
|
||||
throw new OperationError("Invalid ELF");
|
||||
|
||||
ehResult.push("Magic:".padEnd(align) + `${Utils.byteArrayToChars(magic)}`);
|
||||
|
||||
format = stream.readInt(1);
|
||||
ehResult.push("Format:".padEnd(align) + `${format === 1 ? "32-bit" : "64-bit"}`);
|
||||
|
||||
endianness = stream.readInt(1) === 1 ? "le" : "be";
|
||||
ehResult.push("Endianness:".padEnd(align) + `${endianness === "le" ? "Little" : "Big"}`);
|
||||
|
||||
ehResult.push("Version:".padEnd(align) + `${stream.readInt(1).toString()}`);
|
||||
|
||||
let ABI = "";
|
||||
switch (stream.readInt(1)) {
|
||||
case 0x00:
|
||||
ABI = "System V";
|
||||
break;
|
||||
case 0x01:
|
||||
ABI = "HP-UX";
|
||||
break;
|
||||
case 0x02:
|
||||
ABI = "NetBSD";
|
||||
break;
|
||||
case 0x03:
|
||||
ABI = "Linux";
|
||||
break;
|
||||
case 0x04:
|
||||
ABI = "GNU Hurd";
|
||||
break;
|
||||
case 0x06:
|
||||
ABI = "Solaris";
|
||||
break;
|
||||
case 0x07:
|
||||
ABI = "AIX";
|
||||
break;
|
||||
case 0x08:
|
||||
ABI = "IRIX";
|
||||
break;
|
||||
case 0x09:
|
||||
ABI = "FreeBSD";
|
||||
break;
|
||||
case 0x0A:
|
||||
ABI = "Tru64";
|
||||
break;
|
||||
case 0x0B:
|
||||
ABI = "Novell Modesto";
|
||||
break;
|
||||
case 0x0C:
|
||||
ABI = "OpenBSD";
|
||||
break;
|
||||
case 0x0D:
|
||||
ABI = "OpenVMS";
|
||||
break;
|
||||
case 0x0E:
|
||||
ABI = "NonStop Kernel";
|
||||
break;
|
||||
case 0x0F:
|
||||
ABI = "AROS";
|
||||
break;
|
||||
case 0x10:
|
||||
ABI = "Fenix OS";
|
||||
break;
|
||||
case 0x11:
|
||||
ABI = "CloudABI";
|
||||
break;
|
||||
case 0x12:
|
||||
ABI = "Stratus Technologies OpenVOS";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ehResult.push("ABI:".padEnd(align) + ABI);
|
||||
|
||||
// Linux Kernel does not use ABI Version.
|
||||
const abiVersion = stream.readInt(1).toString();
|
||||
if (ABI !== "Linux")
|
||||
ehResult.push("ABI Version:".padEnd(align) + abiVersion);
|
||||
|
||||
stream.moveForwardsBy(7);
|
||||
|
||||
let eType = "";
|
||||
switch (stream.readInt(2, endianness)) {
|
||||
case 0x0000:
|
||||
eType = "Unknown";
|
||||
break;
|
||||
case 0x0001:
|
||||
eType = "Relocatable File";
|
||||
break;
|
||||
case 0x0002:
|
||||
eType = "Executable File";
|
||||
break;
|
||||
case 0x0003:
|
||||
eType = "Shared Object";
|
||||
break;
|
||||
case 0x0004:
|
||||
eType = "Core File";
|
||||
break;
|
||||
case 0xFE00:
|
||||
eType = "LOOS";
|
||||
break;
|
||||
case 0xFEFF:
|
||||
eType = "HIOS";
|
||||
break;
|
||||
case 0xFF00:
|
||||
eType = "LOPROC";
|
||||
break;
|
||||
case 0xFFFF:
|
||||
eType = "HIPROC";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ehResult.push("Type:".padEnd(align) + eType);
|
||||
|
||||
let ISA = "";
|
||||
switch (stream.readInt(2, endianness)) {
|
||||
case 0x0000:
|
||||
ISA = "No specific instruction set";
|
||||
break;
|
||||
case 0x0001:
|
||||
ISA = "AT&T WE 32100";
|
||||
break;
|
||||
case 0x0002:
|
||||
ISA = "SPARC";
|
||||
break;
|
||||
case 0x0003:
|
||||
ISA = "x86";
|
||||
break;
|
||||
case 0x0004:
|
||||
ISA = "Motorola 68000 (M68k)";
|
||||
break;
|
||||
case 0x0005:
|
||||
ISA = "Motorola 88000 (M88k)";
|
||||
break;
|
||||
case 0x0006:
|
||||
ISA = "Intel MCU";
|
||||
break;
|
||||
case 0x0007:
|
||||
ISA = "Intel 80860";
|
||||
break;
|
||||
case 0x0008:
|
||||
ISA = "MIPS";
|
||||
break;
|
||||
case 0x0009:
|
||||
ISA = "IBM System/370";
|
||||
break;
|
||||
case 0x000A:
|
||||
ISA = "MIPS RS3000 Little-endian";
|
||||
break;
|
||||
case 0x000B:
|
||||
case 0x000C:
|
||||
case 0x000D:
|
||||
case 0x000E:
|
||||
case 0x0018:
|
||||
case 0x0019:
|
||||
case 0x001A:
|
||||
case 0x001B:
|
||||
case 0x001C:
|
||||
case 0x001D:
|
||||
case 0x001E:
|
||||
case 0x001F:
|
||||
case 0x0020:
|
||||
case 0x0021:
|
||||
case 0x0022:
|
||||
case 0x0023:
|
||||
ISA = "Reserved for future use";
|
||||
break;
|
||||
case 0x000F:
|
||||
ISA = "Hewlett-Packard PA-RISC";
|
||||
break;
|
||||
case 0x0011:
|
||||
ISA = "Fujitsu VPP500";
|
||||
break;
|
||||
case 0x0012:
|
||||
ISA = "Enhanced instruction set SPARC";
|
||||
break;
|
||||
case 0x0013:
|
||||
ISA = "Intel 80960";
|
||||
break;
|
||||
case 0x0014:
|
||||
ISA = "PowerPC";
|
||||
break;
|
||||
case 0x0015:
|
||||
ISA = "PowerPC (64-bit)";
|
||||
break;
|
||||
case 0x0016:
|
||||
ISA = "S390, including S390";
|
||||
break;
|
||||
case 0x0017:
|
||||
ISA = "IBM SPU/SPC";
|
||||
break;
|
||||
case 0x0024:
|
||||
ISA = "NEC V800";
|
||||
break;
|
||||
case 0x0025:
|
||||
ISA = "Fujitsu FR20";
|
||||
break;
|
||||
case 0x0026:
|
||||
ISA = "TRW RH-32";
|
||||
break;
|
||||
case 0x0027:
|
||||
ISA = "Motorola RCE";
|
||||
break;
|
||||
case 0x0028:
|
||||
ISA = "ARM (up to ARMv7/Aarch32)";
|
||||
break;
|
||||
case 0x0029:
|
||||
ISA = "Digital Alpha";
|
||||
break;
|
||||
case 0x002A:
|
||||
ISA = "SuperH";
|
||||
break;
|
||||
case 0x002B:
|
||||
ISA = "SPARC Version 9";
|
||||
break;
|
||||
case 0x002C:
|
||||
ISA = "Siemens TriCore embedded processor";
|
||||
break;
|
||||
case 0x002D:
|
||||
ISA = "Argonaut RISC Core";
|
||||
break;
|
||||
case 0x002E:
|
||||
ISA = "Hitachi H8/300";
|
||||
break;
|
||||
case 0x002F:
|
||||
ISA = "Hitachi H8/300H";
|
||||
break;
|
||||
case 0x0030:
|
||||
ISA = "Hitachi H8S";
|
||||
break;
|
||||
case 0x0031:
|
||||
ISA = "Hitachi H8/500";
|
||||
break;
|
||||
case 0x0032:
|
||||
ISA = "IA-64";
|
||||
break;
|
||||
case 0x0033:
|
||||
ISA = "Standford MIPS-X";
|
||||
break;
|
||||
case 0x0034:
|
||||
ISA = "Motorola ColdFire";
|
||||
break;
|
||||
case 0x0035:
|
||||
ISA = "Motorola M68HC12";
|
||||
break;
|
||||
case 0x0036:
|
||||
ISA = "Fujitsu MMA Multimedia Accelerator";
|
||||
break;
|
||||
case 0x0037:
|
||||
ISA = "Siemens PCP";
|
||||
break;
|
||||
case 0x0038:
|
||||
ISA = "Sony nCPU embedded RISC processor";
|
||||
break;
|
||||
case 0x0039:
|
||||
ISA = "Denso NDR1 microprocessor";
|
||||
break;
|
||||
case 0x003A:
|
||||
ISA = "Motorola Star*Core processor";
|
||||
break;
|
||||
case 0x003B:
|
||||
ISA = "Toyota ME16 processor";
|
||||
break;
|
||||
case 0x003C:
|
||||
ISA = "STMicroelectronics ST100 processor";
|
||||
break;
|
||||
case 0x003D:
|
||||
ISA = "Advanced Logic Corp. TinyJ embedded processor family";
|
||||
break;
|
||||
case 0x003E:
|
||||
ISA = "AMD x86-64";
|
||||
break;
|
||||
case 0x003F:
|
||||
ISA = "Sony DSP Processor";
|
||||
break;
|
||||
case 0x0040:
|
||||
ISA = "Digital Equipment Corp. PDP-10";
|
||||
break;
|
||||
case 0x0041:
|
||||
ISA = "Digital Equipment Corp. PDP-11";
|
||||
break;
|
||||
case 0x0042:
|
||||
ISA = "Siemens FX66 microcontroller";
|
||||
break;
|
||||
case 0x0043:
|
||||
ISA = "STMicroelectronics ST9+ 8/16 bit microcontroller";
|
||||
break;
|
||||
case 0x0044:
|
||||
ISA = "STMicroelectronics ST7 8-bit microcontroller";
|
||||
break;
|
||||
case 0x0045:
|
||||
ISA = "Motorola MC68HC16 Microcontroller";
|
||||
break;
|
||||
case 0x0046:
|
||||
ISA = "Motorola MC68HC11 Microcontroller";
|
||||
break;
|
||||
case 0x0047:
|
||||
ISA = "Motorola MC68HC08 Microcontroller";
|
||||
break;
|
||||
case 0x0048:
|
||||
ISA = "Motorola MC68HC05 Microcontroller";
|
||||
break;
|
||||
case 0x0049:
|
||||
ISA = "Silicon Graphics SVx";
|
||||
break;
|
||||
case 0x004A:
|
||||
ISA = "STMicroelectronics ST19 8-bit microcontroller";
|
||||
break;
|
||||
case 0x004B:
|
||||
ISA = "Digital VAX";
|
||||
break;
|
||||
case 0x004C:
|
||||
ISA = "Axis Communications 32-bit embedded processor";
|
||||
break;
|
||||
case 0x004D:
|
||||
ISA = "Infineon Technologies 32-bit embedded processor";
|
||||
break;
|
||||
case 0x004E:
|
||||
ISA = "Element 14 64-bit DSP Processor";
|
||||
break;
|
||||
case 0x004F:
|
||||
ISA = "LSI Logic 16-bit DSP Processor";
|
||||
break;
|
||||
case 0x0050:
|
||||
ISA = "Donald Knuth's educational 64-bit processor";
|
||||
break;
|
||||
case 0x0051:
|
||||
ISA = "Harvard University machine-independent object files";
|
||||
break;
|
||||
case 0x0052:
|
||||
ISA = "SiTera Prism";
|
||||
break;
|
||||
case 0x0053:
|
||||
ISA = "Atmel AVR 8-bit microcontroller";
|
||||
break;
|
||||
case 0x0054:
|
||||
ISA = "Fujitsu FR30";
|
||||
break;
|
||||
case 0x0055:
|
||||
ISA = "Mitsubishi D10V";
|
||||
break;
|
||||
case 0x0056:
|
||||
ISA = "Mitsubishi D30V";
|
||||
break;
|
||||
case 0x0057:
|
||||
ISA = "NEC v850";
|
||||
break;
|
||||
case 0x0058:
|
||||
ISA = "Mitsubishi M32R";
|
||||
break;
|
||||
case 0x0059:
|
||||
ISA = "Matsushita MN10300";
|
||||
break;
|
||||
case 0x005A:
|
||||
ISA = "Matsushita MN10200";
|
||||
break;
|
||||
case 0x005B:
|
||||
ISA = "picoJava";
|
||||
break;
|
||||
case 0x005C:
|
||||
ISA = "OpenRISC 32-bit embedded processor";
|
||||
break;
|
||||
case 0x005D:
|
||||
ISA = "ARC Cores Tangent-A5";
|
||||
break;
|
||||
case 0x005E:
|
||||
ISA = "Tensilica Xtensa Architecture";
|
||||
break;
|
||||
case 0x005F:
|
||||
ISA = "Alphamosaic VideoCore processor";
|
||||
break;
|
||||
case 0x0060:
|
||||
ISA = "Thompson Multimedia General Purpose Processor";
|
||||
break;
|
||||
case 0x0061:
|
||||
ISA = "National Semiconductor 32000 series";
|
||||
break;
|
||||
case 0x0062:
|
||||
ISA = "Tenor Network TPC processor";
|
||||
break;
|
||||
case 0x0063:
|
||||
ISA = "Trebia SNP 1000 processor";
|
||||
break;
|
||||
case 0x0064:
|
||||
ISA = "STMicroelectronics (www.st.com) ST200 microcontroller";
|
||||
break;
|
||||
case 0x008C:
|
||||
ISA = "TMS320C6000 Family";
|
||||
break;
|
||||
case 0x00AF:
|
||||
ISA = "MCST Elbrus e2k";
|
||||
break;
|
||||
case 0x00B7:
|
||||
ISA = "ARM 64-bits (ARMv8/Aarch64)";
|
||||
break;
|
||||
case 0x00F3:
|
||||
ISA = "RISC-V";
|
||||
break;
|
||||
case 0x00F7:
|
||||
ISA = "Berkeley Packet Filter";
|
||||
break;
|
||||
case 0x0101:
|
||||
ISA = "WDC 65C816";
|
||||
break;
|
||||
default:
|
||||
ISA = "Unimplemented";
|
||||
break;
|
||||
}
|
||||
ehResult.push("Instruction Set Architecture:".padEnd(align) + ISA);
|
||||
|
||||
ehResult.push("ELF Version:".padEnd(align) + `${stream.readInt(4, endianness)}`);
|
||||
|
||||
const readSize = format === 1 ? 4 : 8;
|
||||
entry = stream.readInt(readSize, endianness);
|
||||
phoff = stream.readInt(readSize, endianness);
|
||||
shoff = stream.readInt(readSize, endianness);
|
||||
ehResult.push("Entry Point:".padEnd(align) + `0x${Utils.hex(entry)}`);
|
||||
ehResult.push("Entry PHOFF:".padEnd(align) + `0x${Utils.hex(phoff)}`);
|
||||
ehResult.push("Entry SHOFF:".padEnd(align) + `0x${Utils.hex(shoff)}`);
|
||||
|
||||
const flags = stream.readInt(4, endianness);
|
||||
ehResult.push("Flags:".padEnd(align) + `${Utils.bin(flags)}`);
|
||||
|
||||
ehResult.push("ELF Header Size:".padEnd(align) + `${stream.readInt(2, endianness)} bytes`);
|
||||
ehResult.push("Program Header Size:".padEnd(align) + `${stream.readInt(2, endianness)} bytes`);
|
||||
phEntries = stream.readInt(2, endianness);
|
||||
ehResult.push("Program Header Entries:".padEnd(align) + phEntries);
|
||||
shentSize = stream.readInt(2, endianness);
|
||||
ehResult.push("Section Header Size:".padEnd(align) + shentSize + " bytes");
|
||||
shEntries = stream.readInt(2, endianness);
|
||||
ehResult.push("Section Header Entries:".padEnd(align) + shEntries);
|
||||
shstrtab = stream.readInt(2, endianness);
|
||||
ehResult.push("Section Header Names:".padEnd(align) + shstrtab);
|
||||
|
||||
return ehResult.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* This function parses and extracts relevant information from a Program Header.
|
||||
*
|
||||
* @param {stream} stream
|
||||
* @returns {string}
|
||||
*/
|
||||
function programHeader(stream) {
|
||||
/**
|
||||
* A Program Header is comprised of the following structures depending on the binary's format.
|
||||
*
|
||||
* p_type - 4 Bytes identifying the type of the segment.
|
||||
*
|
||||
* 32-bit:
|
||||
* p_offset - 4 Bytes specifying the offset of the segment.
|
||||
* p_vaddr - 4 Bytes specifying the virtual address of the segment in memory.
|
||||
* p_paddr - 4 Bytes specifying the physical address of the segment in memory.
|
||||
* p_filesz - 4 Bytes specifying the size in bytes of the segment in the file image.
|
||||
* p_memsz - 4 Bytes specifying the size in bytes of the segment in memory.
|
||||
* p_flags - 4 Bytes identifying the segment dependent flags.
|
||||
* p_align - 4 Bytes set to 0 or 1 for alignment or no alignment, respectively.
|
||||
*
|
||||
* 64-bit:
|
||||
* p_flags - 4 Bytes identifying segment dependent flags.
|
||||
* p_offset - 8 Bytes specifying the offset of the segment.
|
||||
* p_vaddr - 8 Bytes specifying the virtual address of the segment in memory.
|
||||
* p_paddr - 8 Bytes specifying the physical address of the segment in memory.
|
||||
* p_filesz - 8 Bytes specifying the size in bytes of the segment in the file image.
|
||||
* p_memsz - 8 Bytes specifying the size in bytes of the segment in memory.
|
||||
* p_align - 8 Bytes set to 0 or 1 for alignment or no alignment, respectively.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This function decodes the flags bitmask for the Program Header.
|
||||
*
|
||||
* @param {integer} flags
|
||||
* @returns {string}
|
||||
*/
|
||||
function readFlags(flags) {
|
||||
const result = [];
|
||||
if (flags & 0x1)
|
||||
result.push("Execute");
|
||||
if (flags & 0x2)
|
||||
result.push("Write");
|
||||
if (flags & 0x4)
|
||||
result.push("Read");
|
||||
if (flags & 0xf0000000)
|
||||
result.push("Unspecified");
|
||||
return result.join(",");
|
||||
}
|
||||
|
||||
const phResult = [];
|
||||
|
||||
let pType = "";
|
||||
const programHeaderType = stream.readInt(4, endianness);
|
||||
switch (true) {
|
||||
case (programHeaderType === 0x00000000):
|
||||
pType = "Unused";
|
||||
break;
|
||||
case (programHeaderType === 0x00000001):
|
||||
pType = "Loadable Segment";
|
||||
break;
|
||||
case (programHeaderType === 0x00000002):
|
||||
pType = "Dynamic linking information";
|
||||
break;
|
||||
case (programHeaderType === 0x00000003):
|
||||
pType = "Interpreter Information";
|
||||
break;
|
||||
case (programHeaderType === 0x00000004):
|
||||
pType = "Auxiliary Information";
|
||||
break;
|
||||
case (programHeaderType === 0x00000005):
|
||||
pType = "Reserved";
|
||||
break;
|
||||
case (programHeaderType === 0x00000006):
|
||||
pType = "Program Header Table";
|
||||
break;
|
||||
case (programHeaderType === 0x00000007):
|
||||
pType = "Thread-Local Storage Template";
|
||||
break;
|
||||
case (programHeaderType >= 0x60000000 && programHeaderType <= 0x6FFFFFFF):
|
||||
pType = "Reserved Inclusive Range. OS Specific";
|
||||
break;
|
||||
case (programHeaderType >= 0x70000000 && programHeaderType <= 0x7FFFFFFF):
|
||||
pType = "Reserved Inclusive Range. Processor Specific";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
phResult.push("Program Header Type:".padEnd(align) + pType);
|
||||
|
||||
if (format === 2)
|
||||
phResult.push("Flags:".padEnd(align) + readFlags(stream.readInt(4, endianness)));
|
||||
|
||||
const readSize = format === 1? 4 : 8;
|
||||
phResult.push("Offset Of Segment:".padEnd(align) + `${stream.readInt(readSize, endianness)}`);
|
||||
phResult.push("Virtual Address of Segment:".padEnd(align) + `${stream.readInt(readSize, endianness)}`);
|
||||
phResult.push("Physical Address of Segment:".padEnd(align) + `${stream.readInt(readSize, endianness)}`);
|
||||
phResult.push("Size of Segment:".padEnd(align) + `${stream.readInt(readSize, endianness)} bytes`);
|
||||
phResult.push("Size of Segment in Memory:".padEnd(align) + `${stream.readInt(readSize, endianness)} bytes`);
|
||||
|
||||
if (format === 1)
|
||||
phResult.push("Flags:".padEnd(align) + readFlags(stream.readInt(4, endianness)));
|
||||
|
||||
stream.moveForwardsBy(readSize);
|
||||
|
||||
return phResult.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* This function parses and extracts relevant information from a Section Header.
|
||||
*
|
||||
* @param {stream} stream
|
||||
* @returns {string}
|
||||
*/
|
||||
function sectionHeader(stream) {
|
||||
/**
|
||||
* A Section Header is comprised of the following structures depending on the binary's format.
|
||||
*
|
||||
* sh_name - 4 Bytes identifying the offset into the .shstrtab for the name of this section.
|
||||
* sh_type - 4 Bytes identifying the type of this header.
|
||||
*
|
||||
* 32-bit:
|
||||
* sh_flags - 4 Bytes identifying section specific flags.
|
||||
* sh_addr - 4 Bytes identifying the virtual address of the section in memory.
|
||||
* sh_offset - 4 Bytes identifying the offset of the section in the file.
|
||||
* sh_size - 4 Bytes specifying the size in bytes of the section in the file image.
|
||||
* sh_link - 4 Bytes identifying the index of an associated section.
|
||||
* sh_info - 4 Bytes specifying extra information about the section.
|
||||
* sh_addralign - 4 Bytes containing the alignment for the section.
|
||||
* sh_entsize - 4 Bytes specifying the size, in bytes, of each entry in the section.
|
||||
*
|
||||
* 64-bit:
|
||||
* sh_flags - 8 Bytes identifying section specific flags.
|
||||
* sh_addr - 8 Bytes identifying the virtual address of the section in memory.
|
||||
* sh_offset - 8 Bytes identifying the offset of the section in the file.
|
||||
* sh_size - 8 Bytes specifying the size in bytes of the section in the file image.
|
||||
* sh_link - 4 Bytes identifying the index of an associated section.
|
||||
* sh_info - 4 Bytes specifying extra information about the section.
|
||||
* sh_addralign - 8 Bytes containing the alignment for the section.
|
||||
* sh_entsize - 8 Bytes specifying the size, in bytes, of each entry in the section.
|
||||
*/
|
||||
const shResult = [];
|
||||
|
||||
const nameOffset = stream.readInt(4, endianness);
|
||||
let type = "";
|
||||
const shType = stream.readInt(4, endianness);
|
||||
switch (true) {
|
||||
case (shType === 0x00000001):
|
||||
type = "Program Data";
|
||||
break;
|
||||
case (shType === 0x00000002):
|
||||
type = "Symbol Table";
|
||||
break;
|
||||
case (shType === 0x00000003):
|
||||
type = "String Table";
|
||||
break;
|
||||
case (shType === 0x00000004):
|
||||
type = "Relocation Entries with Addens";
|
||||
break;
|
||||
case (shType === 0x00000005):
|
||||
type = "Symbol Hash Table";
|
||||
break;
|
||||
case (shType === 0x00000006):
|
||||
type = "Dynamic Linking Information";
|
||||
break;
|
||||
case (shType === 0x00000007):
|
||||
type = "Notes";
|
||||
break;
|
||||
case (shType === 0x00000008):
|
||||
type = "Program Space with No Data";
|
||||
break;
|
||||
case (shType === 0x00000009):
|
||||
type = "Relocation Entries with no Addens";
|
||||
break;
|
||||
case (shType === 0x0000000A):
|
||||
type = "Reserved";
|
||||
break;
|
||||
case (shType === 0x0000000B):
|
||||
type = "Dynamic Linker Symbol Table";
|
||||
break;
|
||||
case (shType === 0x0000000E):
|
||||
type = "Array of Constructors";
|
||||
break;
|
||||
case (shType === 0x0000000F):
|
||||
type = "Array of Destructors";
|
||||
break;
|
||||
case (shType === 0x00000010):
|
||||
type = "Array of pre-constructors";
|
||||
break;
|
||||
case (shType === 0x00000011):
|
||||
type = "Section group";
|
||||
break;
|
||||
case (shType === 0x00000012):
|
||||
type = "Extended section indices";
|
||||
break;
|
||||
case (shType === 0x00000013):
|
||||
type = "Number of defined types";
|
||||
break;
|
||||
case (shType >= 0x60000000 && shType <= 0x6fffffff):
|
||||
type = "OS-specific";
|
||||
break;
|
||||
case (shType >= 0x70000000 && shType <= 0x7fffffff):
|
||||
type = "Processor-specific";
|
||||
break;
|
||||
case (shType >= 0x80000000 && shType <= 0x8fffffff):
|
||||
type = "Application-specific";
|
||||
break;
|
||||
default:
|
||||
type = "Unused";
|
||||
break;
|
||||
}
|
||||
|
||||
shResult.push("Type:".padEnd(align) + type);
|
||||
|
||||
let nameResult = "";
|
||||
if (type !== "Unused") {
|
||||
nameResult = readString(stream, namesOffset, nameOffset);
|
||||
shResult.push("Section Name: ".padEnd(align) + nameResult);
|
||||
}
|
||||
|
||||
const readSize = (format === 1) ? 4 : 8;
|
||||
|
||||
const flags = stream.readInt(readSize, endianness);
|
||||
const shFlags = [];
|
||||
const bitMasks = [
|
||||
[0x00000001, "Writable"],
|
||||
[0x00000002, "Alloc"],
|
||||
[0x00000004, "Executable"],
|
||||
[0x00000010, "Merge"],
|
||||
[0x00000020, "Strings"],
|
||||
[0x00000040, "SHT Info Link"],
|
||||
[0x00000080, "Link Order"],
|
||||
[0x00000100, "OS Specific Handling"],
|
||||
[0x00000200, "Group"],
|
||||
[0x00000400, "Thread Local Data"],
|
||||
[0x0FF00000, "OS-Specific"],
|
||||
[0xF0000000, "Processor Specific"],
|
||||
[0x04000000, "Special Ordering (Solaris)"],
|
||||
[0x08000000, "Excluded (Solaris)"]
|
||||
];
|
||||
bitMasks.forEach(elem => {
|
||||
if (flags & elem[0])
|
||||
shFlags.push(elem[1]);
|
||||
});
|
||||
shResult.push("Flags:".padEnd(align) + shFlags);
|
||||
|
||||
const vaddr = stream.readInt(readSize, endianness);
|
||||
shResult.push("Section Vaddr in memory:".padEnd(align) + vaddr);
|
||||
|
||||
const shoffset = stream.readInt(readSize, endianness);
|
||||
shResult.push("Offset of the section:".padEnd(align) + shoffset);
|
||||
|
||||
const secSize = stream.readInt(readSize, endianness);
|
||||
shResult.push("Section Size:".padEnd(align) + secSize);
|
||||
|
||||
const associatedSection = stream.readInt(4, endianness);
|
||||
shResult.push("Associated Section:".padEnd(align) + associatedSection);
|
||||
|
||||
const extraInfo = stream.readInt(4, endianness);
|
||||
shResult.push("Section Extra Information:".padEnd(align) + extraInfo);
|
||||
|
||||
// Jump over alignment field.
|
||||
stream.moveForwardsBy(readSize);
|
||||
const entSize = stream.readInt(readSize, endianness);
|
||||
switch (nameResult) {
|
||||
case ".strtab":
|
||||
strtabOffset = shoffset;
|
||||
break;
|
||||
case ".symtab":
|
||||
symtabOffset = shoffset;
|
||||
symtabSize = secSize;
|
||||
symtabEntSize = entSize;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return shResult.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns the offset of the Section Header Names Section.
|
||||
*
|
||||
* @param {stream} stream
|
||||
*/
|
||||
function getNamesOffset(stream) {
|
||||
const preMove = stream.position;
|
||||
stream.moveTo(shoff + (shentSize * shstrtab));
|
||||
if (format === 1) {
|
||||
stream.moveForwardsBy(0x10);
|
||||
namesOffset = stream.readInt(4, endianness);
|
||||
} else {
|
||||
stream.moveForwardsBy(0x18);
|
||||
namesOffset = stream.readInt(8, endianness);
|
||||
}
|
||||
stream.position = preMove;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns a symbol's name from the string table.
|
||||
*
|
||||
* @param {stream} stream
|
||||
* @returns {string}
|
||||
*/
|
||||
function getSymbols(stream) {
|
||||
/**
|
||||
* The Symbol Table is comprised of Symbol Table Entries whose structure depends on the binary's format.
|
||||
*
|
||||
* 32-bit:
|
||||
* st_name - 4 Bytes specifying an index in the files symbol string table.
|
||||
* st_value - 4 Bytes identifying the value associated with the symbol.
|
||||
* st_size - 4 Bytes specifying the size associated with the symbol (this is not the size of the symbol).
|
||||
* st_info - A byte specifying the type and binding of the symbol.
|
||||
* st_other - A byte specifying the symbol's visibility.
|
||||
* st_shndx - 2 Bytes identifying the section that this symbol is related to.
|
||||
*
|
||||
* 64-bit:
|
||||
* st_name - 4 Bytes specifying an index in the files symbol string table.
|
||||
* st_info - A byte specifying the type and binding of the symbol.
|
||||
* st_other - A byte specifying the symbol's visibility.
|
||||
* st_shndx - 2 Bytes identifying the section that this symbol is related to.
|
||||
* st_value - 8 Bytes identifying the value associated with the symbol.
|
||||
* st_size - 8 Bytes specifying the size associated with the symbol (this is not the size of the symbol).
|
||||
*/
|
||||
const nameOffset = stream.readInt(4, endianness);
|
||||
stream.moveForwardsBy(format === 2 ? 20 : 12);
|
||||
return readString(stream, strtabOffset, nameOffset);
|
||||
}
|
||||
|
||||
input = new Uint8Array(input);
|
||||
const stream = new Stream(input);
|
||||
const result = ["=".repeat(align) + " ELF Header " + "=".repeat(align)];
|
||||
result.push(elfHeader(stream) + "\n");
|
||||
|
||||
getNamesOffset(stream);
|
||||
|
||||
result.push("=".repeat(align) + " Program Header " + "=".repeat(align));
|
||||
stream.moveTo(phoff);
|
||||
for (let i = 0; i < phEntries; i++)
|
||||
result.push(programHeader(stream) + "\n");
|
||||
|
||||
result.push("=".repeat(align) + " Section Header " + "=".repeat(align));
|
||||
stream.moveTo(shoff);
|
||||
for (let i = 0; i < shEntries; i++)
|
||||
result.push(sectionHeader(stream) + "\n");
|
||||
|
||||
result.push("=".repeat(align) + " Symbol Table " + "=".repeat(align));
|
||||
|
||||
stream.moveTo(symtabOffset);
|
||||
let elem = "";
|
||||
for (let i = 0; i < (symtabSize / symtabEntSize); i++)
|
||||
if ((elem = getSymbols(stream)) !== "")
|
||||
result.push("Symbol Name:".padEnd(align) + elem);
|
||||
|
||||
return result.join("\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ELFInfo;
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import Operation from "../Operation.mjs";
|
||||
import cptable from "codepage";
|
||||
import {IO_FORMAT} from "../lib/ChrEnc.mjs";
|
||||
import {CHR_ENC_CODE_PAGES} from "../lib/ChrEnc.mjs";
|
||||
|
||||
/**
|
||||
* Encode text operation
|
||||
|
@ -26,7 +26,7 @@ class EncodeText extends Operation {
|
|||
"<br><br>",
|
||||
"Supported charsets are:",
|
||||
"<ul>",
|
||||
Object.keys(IO_FORMAT).map(e => `<li>${e}</li>`).join("\n"),
|
||||
Object.keys(CHR_ENC_CODE_PAGES).map(e => `<li>${e}</li>`).join("\n"),
|
||||
"</ul>",
|
||||
].join("\n");
|
||||
this.infoURL = "https://wikipedia.org/wiki/Character_encoding";
|
||||
|
@ -36,7 +36,7 @@ class EncodeText extends Operation {
|
|||
{
|
||||
"name": "Encoding",
|
||||
"type": "option",
|
||||
"value": Object.keys(IO_FORMAT)
|
||||
"value": Object.keys(CHR_ENC_CODE_PAGES)
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ class EncodeText extends Operation {
|
|||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
run(input, args) {
|
||||
const format = IO_FORMAT[args[0]];
|
||||
const format = CHR_ENC_CODE_PAGES[args[0]];
|
||||
const encoded = cptable.utils.encode(format, input);
|
||||
return new Uint8Array(encoded).buffer;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
/**
|
||||
* Emulation of the Enigma machine.
|
||||
*
|
||||
* Tested against various genuine Enigma machines using a variety of inputs
|
||||
* and settings to confirm correctness.
|
||||
*
|
||||
* @author s2224834
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
|
|
|
@ -358,7 +358,7 @@ class Entropy extends Operation {
|
|||
|
||||
<br><script>
|
||||
var canvas = document.getElementById("chart-area"),
|
||||
parentRect = canvas.parentNode.getBoundingClientRect(),
|
||||
parentRect = canvas.closest(".cm-scroller").getBoundingClientRect(),
|
||||
entropy = ${entropy},
|
||||
height = parentRect.height * 0.25;
|
||||
|
||||
|
|
|
@ -44,7 +44,13 @@ class ExtractDates extends Operation {
|
|||
date3 = "(?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])[- /.](?:19|20)\\d\\d", // mm/dd/yyyy
|
||||
regex = new RegExp(date1 + "|" + date2 + "|" + date3, "ig");
|
||||
|
||||
return search(input, regex, null, displayTotal);
|
||||
const results = search(input, regex);
|
||||
|
||||
if (displayTotal) {
|
||||
return `Total found: ${results.length}\n\n${results.join("\n")}`;
|
||||
} else {
|
||||
return results.join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { search, DOMAIN_REGEX } from "../lib/Extract.mjs";
|
||||
import { caseInsensitiveSort } from "../lib/Sort.mjs";
|
||||
|
||||
/**
|
||||
* Extract domains operation
|
||||
|
@ -25,9 +26,19 @@ class ExtractDomains extends Operation {
|
|||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Display total",
|
||||
"type": "boolean",
|
||||
"value": true
|
||||
name: "Display total",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "Sort",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "Unique",
|
||||
type: "boolean",
|
||||
value: false
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -38,8 +49,21 @@ class ExtractDomains extends Operation {
|
|||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const displayTotal = args[0];
|
||||
return search(input, DOMAIN_REGEX, null, displayTotal);
|
||||
const [displayTotal, sort, unique] = args;
|
||||
|
||||
const results = search(
|
||||
input,
|
||||
DOMAIN_REGEX,
|
||||
null,
|
||||
sort ? caseInsensitiveSort : null,
|
||||
unique
|
||||
);
|
||||
|
||||
if (displayTotal) {
|
||||
return `Total found: ${results.length}\n\n${results.join("\n")}`;
|
||||
} else {
|
||||
return results.join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { search } from "../lib/Extract.mjs";
|
||||
import { caseInsensitiveSort } from "../lib/Sort.mjs";
|
||||
|
||||
/**
|
||||
* Extract email addresses operation
|
||||
|
@ -25,9 +26,19 @@ class ExtractEmailAddresses extends Operation {
|
|||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Display total",
|
||||
"type": "boolean",
|
||||
"value": false
|
||||
name: "Display total",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "Sort",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "Unique",
|
||||
type: "boolean",
|
||||
value: false
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -38,10 +49,23 @@ class ExtractEmailAddresses extends Operation {
|
|||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const displayTotal = args[0],
|
||||
const [displayTotal, sort, unique] = args,
|
||||
// email regex from: https://www.regextester.com/98066
|
||||
regex = /(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?\.)+[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}\])/ig;
|
||||
return search(input, regex, null, displayTotal);
|
||||
|
||||
const results = search(
|
||||
input,
|
||||
regex,
|
||||
null,
|
||||
sort ? caseInsensitiveSort : null,
|
||||
unique
|
||||
);
|
||||
|
||||
if (displayTotal) {
|
||||
return `Total found: ${results.length}\n\n${results.join("\n")}`;
|
||||
} else {
|
||||
return results.join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { search } from "../lib/Extract.mjs";
|
||||
import { caseInsensitiveSort } from "../lib/Sort.mjs";
|
||||
|
||||
/**
|
||||
* Extract file paths operation
|
||||
|
@ -25,19 +26,29 @@ class ExtractFilePaths extends Operation {
|
|||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Windows",
|
||||
"type": "boolean",
|
||||
"value": true
|
||||
name: "Windows",
|
||||
type: "boolean",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
"name": "UNIX",
|
||||
"type": "boolean",
|
||||
"value": true
|
||||
name: "UNIX",
|
||||
type: "boolean",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
"name": "Display total",
|
||||
"type": "boolean",
|
||||
"value": false
|
||||
name: "Display total",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "Sort",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "Unique",
|
||||
type: "boolean",
|
||||
value: false
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -48,7 +59,7 @@ class ExtractFilePaths extends Operation {
|
|||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [includeWinPath, includeUnixPath, displayTotal] = args,
|
||||
const [includeWinPath, includeUnixPath, displayTotal, sort, unique] = args,
|
||||
winDrive = "[A-Z]:\\\\",
|
||||
winName = "[A-Z\\d][A-Z\\d\\- '_\\(\\)~]{0,61}",
|
||||
winExt = "[A-Z\\d]{1,6}",
|
||||
|
@ -65,12 +76,25 @@ class ExtractFilePaths extends Operation {
|
|||
filePaths = unixPath;
|
||||
}
|
||||
|
||||
if (filePaths) {
|
||||
const regex = new RegExp(filePaths, "ig");
|
||||
return search(input, regex, null, displayTotal);
|
||||
} else {
|
||||
if (!filePaths) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const regex = new RegExp(filePaths, "ig");
|
||||
const results = search(
|
||||
input,
|
||||
regex,
|
||||
null,
|
||||
sort ? caseInsensitiveSort : null,
|
||||
unique
|
||||
);
|
||||
|
||||
if (displayTotal) {
|
||||
return `Total found: ${results.length}\n\n${results.join("\n")}`;
|
||||
} else {
|
||||
return results.join("\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -38,8 +38,8 @@ class ExtractFiles extends Operation {
|
|||
<li>
|
||||
${supportedExts.join("</li><li>")}
|
||||
</li>
|
||||
</ul>`;
|
||||
this.infoURL = "https://forensicswiki.xyz/wiki/index.php?title=File_Carving";
|
||||
</ul>Minimum File Size can be used to prune small false positives.`;
|
||||
this.infoURL = "https://forensics.wiki/file_carving";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "List<File>";
|
||||
this.presentType = "html";
|
||||
|
@ -54,6 +54,11 @@ class ExtractFiles extends Operation {
|
|||
name: "Ignore failed extractions",
|
||||
type: "boolean",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: "Minimum File Size",
|
||||
type: "number",
|
||||
value: 100
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
@ -66,6 +71,7 @@ class ExtractFiles extends Operation {
|
|||
run(input, args) {
|
||||
const bytes = new Uint8Array(input),
|
||||
categories = [],
|
||||
minSize = args.pop(1),
|
||||
ignoreFailedExtractions = args.pop(1);
|
||||
|
||||
args.forEach((cat, i) => {
|
||||
|
@ -80,7 +86,9 @@ class ExtractFiles extends Operation {
|
|||
const errors = [];
|
||||
detectedFiles.forEach(detectedFile => {
|
||||
try {
|
||||
files.push(extractFile(bytes, detectedFile.fileDetails, detectedFile.offset));
|
||||
const file = extractFile(bytes, detectedFile.fileDetails, detectedFile.offset);
|
||||
if (file.size >= minSize)
|
||||
files.push(file);
|
||||
} catch (err) {
|
||||
if (!ignoreFailedExtractions && err.message.indexOf("No extraction algorithm available") < 0) {
|
||||
errors.push(
|
||||
|
|
84
src/core/operations/ExtractHashes.mjs
Normal file
84
src/core/operations/ExtractHashes.mjs
Normal file
|
@ -0,0 +1,84 @@
|
|||
/**
|
||||
* @author mshwed [m@ttshwed.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { search } from "../lib/Extract.mjs";
|
||||
|
||||
/**
|
||||
* Extract Hash Values operation
|
||||
*/
|
||||
class ExtractHashes extends Operation {
|
||||
|
||||
/**
|
||||
* ExtractHashValues constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Extract hashes";
|
||||
this.module = "Regex";
|
||||
this.description = "Extracts potential hashes based on hash character length";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Comparison_of_cryptographic_hash_functions";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Hash character length",
|
||||
type: "number",
|
||||
value: 40
|
||||
},
|
||||
{
|
||||
name: "All hashes",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "Display Total",
|
||||
type: "boolean",
|
||||
value: false
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const results = [];
|
||||
let hashCount = 0;
|
||||
|
||||
const [hashLength, searchAllHashes, showDisplayTotal] = args;
|
||||
|
||||
// Convert character length to bit length
|
||||
let hashBitLengths = [(hashLength / 2) * 8];
|
||||
|
||||
if (searchAllHashes) hashBitLengths = [4, 8, 16, 32, 64, 128, 160, 192, 224, 256, 320, 384, 512, 1024];
|
||||
|
||||
for (const hashBitLength of hashBitLengths) {
|
||||
// Convert bit length to character length
|
||||
const hashCharacterLength = (hashBitLength / 8) * 2;
|
||||
|
||||
const regex = new RegExp(`(\\b|^)[a-f0-9]{${hashCharacterLength}}(\\b|$)`, "g");
|
||||
const searchResults = search(input, regex, null, false);
|
||||
|
||||
hashCount += searchResults.length;
|
||||
results.push(...searchResults);
|
||||
}
|
||||
|
||||
let output = "";
|
||||
if (showDisplayTotal) {
|
||||
output = `Total Results: ${hashCount}\n\n`;
|
||||
}
|
||||
|
||||
output = output + results.join("\n");
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ExtractHashes;
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { search } from "../lib/Extract.mjs";
|
||||
import { ipSort } from "../lib/Sort.mjs";
|
||||
|
||||
/**
|
||||
* Extract IP addresses operation
|
||||
|
@ -25,24 +26,34 @@ class ExtractIPAddresses extends Operation {
|
|||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "IPv4",
|
||||
"type": "boolean",
|
||||
"value": true
|
||||
name: "IPv4",
|
||||
type: "boolean",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
"name": "IPv6",
|
||||
"type": "boolean",
|
||||
"value": false
|
||||
name: "IPv6",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
"name": "Remove local IPv4 addresses",
|
||||
"type": "boolean",
|
||||
"value": false
|
||||
name: "Remove local IPv4 addresses",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
"name": "Display total",
|
||||
"type": "boolean",
|
||||
"value": false
|
||||
name: "Display total",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "Sort",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "Unique",
|
||||
type: "boolean",
|
||||
value: false
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -53,9 +64,9 @@ class ExtractIPAddresses extends Operation {
|
|||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [includeIpv4, includeIpv6, removeLocal, displayTotal] = args,
|
||||
const [includeIpv4, includeIpv6, removeLocal, displayTotal, sort, unique] = args,
|
||||
ipv4 = "(?:(?:\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d|\\d)(?:\\/\\d{1,2})?",
|
||||
ipv6 = "((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})((([\\dA-F]{1,4}((?!\\3)::|:\\b|(?![\\dA-F])))|(?!\\2\\3)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})";
|
||||
ipv6 = "((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})(([\\dA-F]{1,4}((?!\\3)::|:\\b|(?![\\dA-F])))|(?!\\2\\3)){2}";
|
||||
let ips = "";
|
||||
|
||||
if (includeIpv4 && includeIpv6) {
|
||||
|
@ -66,23 +77,29 @@ class ExtractIPAddresses extends Operation {
|
|||
ips = ipv6;
|
||||
}
|
||||
|
||||
if (ips) {
|
||||
const regex = new RegExp(ips, "ig");
|
||||
if (!ips) return "";
|
||||
|
||||
if (removeLocal) {
|
||||
const ten = "10\\..+",
|
||||
oneninetwo = "192\\.168\\..+",
|
||||
oneseventwo = "172\\.(?:1[6-9]|2\\d|3[01])\\..+",
|
||||
onetwoseven = "127\\..+",
|
||||
removeRegex = new RegExp("^(?:" + ten + "|" + oneninetwo +
|
||||
"|" + oneseventwo + "|" + onetwoseven + ")");
|
||||
const regex = new RegExp(ips, "ig");
|
||||
|
||||
return search(input, regex, removeRegex, displayTotal);
|
||||
} else {
|
||||
return search(input, regex, null, displayTotal);
|
||||
}
|
||||
const ten = "10\\..+",
|
||||
oneninetwo = "192\\.168\\..+",
|
||||
oneseventwo = "172\\.(?:1[6-9]|2\\d|3[01])\\..+",
|
||||
onetwoseven = "127\\..+",
|
||||
removeRegex = new RegExp("^(?:" + ten + "|" + oneninetwo +
|
||||
"|" + oneseventwo + "|" + onetwoseven + ")");
|
||||
|
||||
const results = search(
|
||||
input,
|
||||
regex,
|
||||
removeLocal ? removeRegex : null,
|
||||
sort ? ipSort : null,
|
||||
unique
|
||||
);
|
||||
|
||||
if (displayTotal) {
|
||||
return `Total found: ${results.length}\n\n${results.join("\n")}`;
|
||||
} else {
|
||||
return "";
|
||||
return results.join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,8 +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 jimplib from "jimp/es/index.js";
|
||||
const jimp = jimplib.default ? jimplib.default : jimplib;
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Extract LSB operation
|
||||
|
@ -74,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;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { search } from "../lib/Extract.mjs";
|
||||
import { hexadecimalSort } from "../lib/Sort.mjs";
|
||||
|
||||
/**
|
||||
* Extract MAC addresses operation
|
||||
|
@ -25,9 +26,19 @@ class ExtractMACAddresses extends Operation {
|
|||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Display total",
|
||||
"type": "boolean",
|
||||
"value": false
|
||||
name: "Display total",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "Sort",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "Unique",
|
||||
type: "boolean",
|
||||
value: false
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -38,10 +49,21 @@ class ExtractMACAddresses extends Operation {
|
|||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const displayTotal = args[0],
|
||||
regex = /[A-F\d]{2}(?:[:-][A-F\d]{2}){5}/ig;
|
||||
const [displayTotal, sort, unique] = args,
|
||||
regex = /[A-F\d]{2}(?:[:-][A-F\d]{2}){5}/ig,
|
||||
results = search(
|
||||
input,
|
||||
regex,
|
||||
null,
|
||||
sort ? hexadecimalSort : null,
|
||||
unique
|
||||
);
|
||||
|
||||
return search(input, regex, null, displayTotal);
|
||||
if (displayTotal) {
|
||||
return `Total found: ${results.length}\n\n${results.join("\n")}`;
|
||||
} else {
|
||||
return results.join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,8 +7,7 @@
|
|||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import { isImage } from "../lib/FileType.mjs";
|
||||
import jimplib from "jimp/es/index.js";
|
||||
const jimp = jimplib.default ? jimplib.default : jimplib;
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
import {RGBA_DELIM_OPTIONS} from "../lib/Delim.mjs";
|
||||
|
||||
|
@ -53,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);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { search, URL_REGEX } from "../lib/Extract.mjs";
|
||||
import { caseInsensitiveSort } from "../lib/Sort.mjs";
|
||||
|
||||
/**
|
||||
* Extract URLs operation
|
||||
|
@ -25,9 +26,19 @@ class ExtractURLs extends Operation {
|
|||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Display total",
|
||||
"type": "boolean",
|
||||
"value": false
|
||||
name: "Display total",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "Sort",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "Unique",
|
||||
type: "boolean",
|
||||
value: false
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -38,8 +49,20 @@ class ExtractURLs extends Operation {
|
|||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const displayTotal = args[0];
|
||||
return search(input, URL_REGEX, null, displayTotal);
|
||||
const [displayTotal, sort, unique] = args;
|
||||
const results = search(
|
||||
input,
|
||||
URL_REGEX,
|
||||
null,
|
||||
sort ? caseInsensitiveSort : null,
|
||||
unique
|
||||
);
|
||||
|
||||
if (displayTotal) {
|
||||
return `Total found: ${results.length}\n\n${results.join("\n")}`;
|
||||
} else {
|
||||
return results.join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
78
src/core/operations/FangURL.mjs
Normal file
78
src/core/operations/FangURL.mjs
Normal file
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
* @author arnydo [github@arnydo.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
|
||||
/**
|
||||
* FangURL operation
|
||||
*/
|
||||
class FangURL extends Operation {
|
||||
|
||||
/**
|
||||
* FangURL constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Fang URL";
|
||||
this.module = "Default";
|
||||
this.description = "Takes a 'Defanged' Universal Resource Locator (URL) and 'Fangs' it. Meaning, it removes the alterations (defanged) that render it useless so that it can be used again.";
|
||||
this.infoURL = "https://isc.sans.edu/forums/diary/Defang+all+the+things/22744/";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Restore [.]",
|
||||
type: "boolean",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: "Restore hxxp",
|
||||
type: "boolean",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: "Restore ://",
|
||||
type: "boolean",
|
||||
value: true
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [dots, http, slashes] = args;
|
||||
|
||||
input = fangURL(input, dots, http, slashes);
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Defangs a given URL
|
||||
*
|
||||
* @param {string} url
|
||||
* @param {boolean} dots
|
||||
* @param {boolean} http
|
||||
* @param {boolean} slashes
|
||||
* @returns {string}
|
||||
*/
|
||||
function fangURL(url, dots, http, slashes) {
|
||||
if (dots) url = url.replace(/\[\.\]/g, ".");
|
||||
if (http) url = url.replace(/hxxp/g, "http");
|
||||
if (slashes) url = url.replace(/\[:\/\/\]/g, "://");
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
export default FangURL;
|
63
src/core/operations/FernetDecrypt.mjs
Normal file
63
src/core/operations/FernetDecrypt.mjs
Normal file
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* @author Karsten Silkenbäumer [github.com/kassi]
|
||||
* @copyright Karsten Silkenbäumer 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import fernet from "fernet";
|
||||
|
||||
/**
|
||||
* FernetDecrypt operation
|
||||
*/
|
||||
class FernetDecrypt extends Operation {
|
||||
/**
|
||||
* FernetDecrypt constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Fernet Decrypt";
|
||||
this.module = "Default";
|
||||
this.description = "Fernet is a symmetric encryption method which makes sure that the message encrypted cannot be manipulated/read without the key. It uses URL safe encoding for the keys. Fernet uses 128-bit AES in CBC mode and PKCS7 padding, with HMAC using SHA256 for authentication. The IV is created from os.random().<br><br><b>Key:</b> The key must be 32 bytes (256 bits) encoded with Base64.";
|
||||
this.infoURL = "https://asecuritysite.com/encryption/fer";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Key",
|
||||
"type": "string",
|
||||
"value": ""
|
||||
},
|
||||
];
|
||||
this.patterns = [
|
||||
{
|
||||
match: "^[A-Z\\d\\-_=]{20,}$",
|
||||
flags: "i",
|
||||
args: []
|
||||
},
|
||||
];
|
||||
}
|
||||
/**
|
||||
* @param {String} input
|
||||
* @param {Object[]} args
|
||||
* @returns {String}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [secretInput] = args;
|
||||
try {
|
||||
const secret = new fernet.Secret(secretInput);
|
||||
const token = new fernet.Token({
|
||||
secret: secret,
|
||||
token: input,
|
||||
ttl: 0
|
||||
});
|
||||
return token.decode();
|
||||
} catch (err) {
|
||||
throw new OperationError(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default FernetDecrypt;
|
54
src/core/operations/FernetEncrypt.mjs
Normal file
54
src/core/operations/FernetEncrypt.mjs
Normal file
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* @author Karsten Silkenbäumer [github.com/kassi]
|
||||
* @copyright Karsten Silkenbäumer 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import fernet from "fernet";
|
||||
|
||||
/**
|
||||
* FernetEncrypt operation
|
||||
*/
|
||||
class FernetEncrypt extends Operation {
|
||||
/**
|
||||
* FernetEncrypt constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Fernet Encrypt";
|
||||
this.module = "Default";
|
||||
this.description = "Fernet is a symmetric encryption method which makes sure that the message encrypted cannot be manipulated/read without the key. It uses URL safe encoding for the keys. Fernet uses 128-bit AES in CBC mode and PKCS7 padding, with HMAC using SHA256 for authentication. The IV is created from os.random().<br><br><b>Key:</b> The key must be 32 bytes (256 bits) encoded with Base64.";
|
||||
this.infoURL = "https://asecuritysite.com/encryption/fer";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Key",
|
||||
"type": "string",
|
||||
"value": ""
|
||||
},
|
||||
];
|
||||
}
|
||||
/**
|
||||
* @param {String} input
|
||||
* @param {Object[]} args
|
||||
* @returns {String}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [secretInput] = args;
|
||||
try {
|
||||
const secret = new fernet.Secret(secretInput);
|
||||
const token = new fernet.Token({
|
||||
secret: secret,
|
||||
});
|
||||
return token.encode(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default FernetEncrypt;
|
94
src/core/operations/FileTree.mjs
Normal file
94
src/core/operations/FileTree.mjs
Normal file
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* @author sw5678
|
||||
* @copyright Crown Copyright 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs";
|
||||
|
||||
/**
|
||||
* Unique operation
|
||||
*/
|
||||
class FileTree extends Operation {
|
||||
|
||||
/**
|
||||
* Unique constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "File Tree";
|
||||
this.module = "Default";
|
||||
this.description = "Creates a file tree from a list of file paths (similar to the tree command in Linux)";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Tree_(command)";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "File Path Delimiter",
|
||||
type: "binaryString",
|
||||
value: "/"
|
||||
},
|
||||
{
|
||||
name: "Delimiter",
|
||||
type: "option",
|
||||
value: INPUT_DELIM_OPTIONS
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
|
||||
// Set up arrow and pipe for nice output display
|
||||
const ARROW = "|---";
|
||||
const PIPE = "| ";
|
||||
|
||||
// Get args from input
|
||||
const fileDelim = args[0];
|
||||
const entryDelim = Utils.charRep(args[1]);
|
||||
|
||||
// Store path to print
|
||||
const completedList = [];
|
||||
const printList = [];
|
||||
|
||||
// Loop through all entries
|
||||
const filePaths = input.split(entryDelim).unique().sort();
|
||||
for (let i = 0; i < filePaths.length; i++) {
|
||||
// Split by file delimiter
|
||||
let path = filePaths[i].split(fileDelim);
|
||||
|
||||
if (path[0] === "") {
|
||||
path = path.slice(1, path.length);
|
||||
}
|
||||
|
||||
for (let j = 0; j < path.length; j++) {
|
||||
let printLine;
|
||||
let key;
|
||||
if (j === 0) {
|
||||
printLine = path[j];
|
||||
key = path[j];
|
||||
} else {
|
||||
printLine = PIPE.repeat(j-1) + ARROW + path[j];
|
||||
key = path.slice(0, j+1).join("/");
|
||||
}
|
||||
|
||||
// Check to see we have already added that path
|
||||
if (!completedList.includes(key)) {
|
||||
completedList.push(key);
|
||||
printList.push(printLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
return printList.join("\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default FileTree;
|
|
@ -35,10 +35,18 @@ class Fletcher32Checksum extends Operation {
|
|||
run(input, args) {
|
||||
let a = 0,
|
||||
b = 0;
|
||||
input = new Uint8Array(input);
|
||||
if (ArrayBuffer.isView(input)) {
|
||||
input = new DataView(input.buffer, input.byteOffset, input.byteLength);
|
||||
} else {
|
||||
input = new DataView(input);
|
||||
}
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
a = (a + input[i]) % 0xffff;
|
||||
for (let i = 0; i < input.byteLength - 1; i += 2) {
|
||||
a = (a + input.getUint16(i, true)) % 0xffff;
|
||||
b = (b + a) % 0xffff;
|
||||
}
|
||||
if (input.byteLength % 2 !== 0) {
|
||||
a = (a + input.getUint8(input.byteLength - 1)) % 0xffff;
|
||||
b = (b + a) % 0xffff;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,10 +35,22 @@ class Fletcher64Checksum extends Operation {
|
|||
run(input, args) {
|
||||
let a = 0,
|
||||
b = 0;
|
||||
input = new Uint8Array(input);
|
||||
if (ArrayBuffer.isView(input)) {
|
||||
input = new DataView(input.buffer, input.byteOffset, input.byteLength);
|
||||
} else {
|
||||
input = new DataView(input);
|
||||
}
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
a = (a + input[i]) % 0xffffffff;
|
||||
for (let i = 0; i < input.byteLength - 3; i += 4) {
|
||||
a = (a + input.getUint32(i, true)) % 0xffffffff;
|
||||
b = (b + a) % 0xffffffff;
|
||||
}
|
||||
if (input.byteLength % 4 !== 0) {
|
||||
let lastValue = 0;
|
||||
for (let i = 0; i < input.byteLength % 4; i++) {
|
||||
lastValue = (lastValue << 8) | input.getUint8(input.byteLength - 1 - i);
|
||||
}
|
||||
a = (a + lastValue) % 0xffffffff;
|
||||
b = (b + a) % 0xffffffff;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,8 +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 jimplib from "jimp/es/index.js";
|
||||
const jimp = jimplib.default ? jimplib.default : jimplib;
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Flip Image operation
|
||||
|
@ -52,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})`);
|
||||
}
|
||||
|
@ -70,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) {
|
||||
|
|
|
@ -65,12 +65,21 @@ class Fork extends Operation {
|
|||
if (input)
|
||||
inputs = input.split(splitDelim);
|
||||
|
||||
// Set to 1 as if we are here, then there is one, the current one.
|
||||
let numOp = 1;
|
||||
// Create subOpList for each tranche to operate on
|
||||
// (all remaining operations unless we encounter a Merge)
|
||||
// all remaining operations unless we encounter a Merge
|
||||
for (i = state.progress + 1; i < opList.length; i++) {
|
||||
if (opList[i].name === "Merge" && !opList[i].disabled) {
|
||||
break;
|
||||
numOp--;
|
||||
if (numOp === 0 || opList[i].ingValues[0])
|
||||
break;
|
||||
else
|
||||
// Not this Fork's Merge.
|
||||
subOpList.push(opList[i]);
|
||||
} else {
|
||||
if (opList[i].name === "Fork" || opList[i].name === "Subsection")
|
||||
numOp++;
|
||||
subOpList.push(opList[i]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,7 +91,7 @@ Number of bytes not represented: ${256 - freq.bytesRepresented}
|
|||
|
||||
<script>
|
||||
var canvas = document.getElementById("chart-area"),
|
||||
parentRect = canvas.parentNode.getBoundingClientRect(),
|
||||
parentRect = canvas.closest(".cm-scroller").getBoundingClientRect(),
|
||||
scores = ${JSON.stringify(freq.percentages)};
|
||||
|
||||
canvas.width = parentRect.width * 0.95;
|
||||
|
|
|
@ -84,7 +84,7 @@ class FromBCD extends Operation {
|
|||
break;
|
||||
case "Raw":
|
||||
default:
|
||||
byteArray = Utils.strToByteArray(input);
|
||||
byteArray = new Uint8Array(Utils.strToArrayBuffer(input));
|
||||
byteArray.forEach(b => {
|
||||
nibbles.push(b >>> 4);
|
||||
nibbles.push(b & 15);
|
||||
|
|
101
src/core/operations/FromBase45.mjs
Normal file
101
src/core/operations/FromBase45.mjs
Normal file
|
@ -0,0 +1,101 @@
|
|||
/**
|
||||
* @author Thomas Weißschuh [thomas@t-8ch.de]
|
||||
* @copyright Crown Copyright 2021
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import {ALPHABET, highlightToBase45, highlightFromBase45} from "../lib/Base45.mjs";
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
|
||||
/**
|
||||
* From Base45 operation
|
||||
*/
|
||||
class FromBase45 extends Operation {
|
||||
|
||||
/**
|
||||
* FromBase45 constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "From Base45";
|
||||
this.module = "Default";
|
||||
this.description = "Base45 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers. The high number base results in shorter strings than with the decimal or hexadecimal system. Base45 is optimized for usage with QR codes.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems";
|
||||
this.inputType = "string";
|
||||
this.outputType = "byteArray";
|
||||
this.args = [
|
||||
{
|
||||
name: "Alphabet",
|
||||
type: "string",
|
||||
value: ALPHABET
|
||||
},
|
||||
{
|
||||
name: "Remove non-alphabet chars",
|
||||
type: "boolean",
|
||||
value: true
|
||||
},
|
||||
];
|
||||
|
||||
this.highlight = highlightFromBase45;
|
||||
this.highlightReverse = highlightToBase45;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
run(input, args) {
|
||||
if (!input) return [];
|
||||
const alphabet = Utils.expandAlphRange(args[0]).join("");
|
||||
const removeNonAlphChars = args[1];
|
||||
|
||||
const res = [];
|
||||
|
||||
// Remove non-alphabet characters
|
||||
if (removeNonAlphChars) {
|
||||
const re = new RegExp("[^" + alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g");
|
||||
input = input.replace(re, "");
|
||||
}
|
||||
|
||||
for (const triple of Utils.chunked(input, 3)) {
|
||||
triple.reverse();
|
||||
let b = 0;
|
||||
for (const c of triple) {
|
||||
const idx = alphabet.indexOf(c);
|
||||
if (idx === -1) {
|
||||
throw new OperationError(`Character not in alphabet: '${c}'`);
|
||||
}
|
||||
b *= 45;
|
||||
b += idx;
|
||||
}
|
||||
|
||||
if (b > 65535) {
|
||||
throw new OperationError(`Triplet too large: '${triple.join("")}'`);
|
||||
}
|
||||
|
||||
if (triple.length > 2) {
|
||||
/**
|
||||
* The last triple may only have 2 bytes so we push the MSB when we got 3 bytes
|
||||
* Pushing MSB
|
||||
*/
|
||||
res.push(b >> 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushing LSB
|
||||
*/
|
||||
res.push(b & 0xff);
|
||||
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default FromBase45;
|
|
@ -60,7 +60,7 @@ class FromBase58 extends Operation {
|
|||
run(input, args) {
|
||||
let alphabet = args[0] || ALPHABET_OPTIONS[0].value;
|
||||
const removeNonAlphaChars = args[1] === undefined ? true : args[1],
|
||||
result = [0];
|
||||
result = [];
|
||||
|
||||
alphabet = Utils.expandAlphRange(alphabet).join("");
|
||||
|
||||
|
@ -87,11 +87,9 @@ class FromBase58 extends Operation {
|
|||
}
|
||||
}
|
||||
|
||||
let carry = result[0] * 58 + index;
|
||||
result[0] = carry & 0xFF;
|
||||
carry = carry >> 8;
|
||||
let carry = index;
|
||||
|
||||
for (let i = 1; i < result.length; i++) {
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
carry += result[i] * 58;
|
||||
result[i] = carry & 0xFF;
|
||||
carry = carry >> 8;
|
||||
|
|
|
@ -34,93 +34,98 @@ class FromBase64 extends Operation {
|
|||
name: "Remove non-alphabet chars",
|
||||
type: "boolean",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: "Strict mode",
|
||||
type: "boolean",
|
||||
value: false
|
||||
}
|
||||
];
|
||||
this.checks = [
|
||||
{
|
||||
pattern: "^\\s*(?:[A-Z\\d+/]{4})+(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$",
|
||||
flags: "i",
|
||||
args: ["A-Za-z0-9+/=", true]
|
||||
args: ["A-Za-z0-9+/=", true, false]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*[A-Z\\d\\-_]{20,}\\s*$",
|
||||
flags: "i",
|
||||
args: ["A-Za-z0-9-_", true]
|
||||
args: ["A-Za-z0-9-_", true, false]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*(?:[A-Z\\d+\\-]{4}){5,}(?:[A-Z\\d+\\-]{2}==|[A-Z\\d+\\-]{3}=)?\\s*$",
|
||||
flags: "i",
|
||||
args: ["A-Za-z0-9+\\-=", true]
|
||||
args: ["A-Za-z0-9+\\-=", true, false]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*(?:[A-Z\\d./]{4}){5,}(?:[A-Z\\d./]{2}==|[A-Z\\d./]{3}=)?\\s*$",
|
||||
flags: "i",
|
||||
args: ["./0-9A-Za-z=", true]
|
||||
args: ["./0-9A-Za-z=", true, false]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*[A-Z\\d_.]{20,}\\s*$",
|
||||
flags: "i",
|
||||
args: ["A-Za-z0-9_.", true]
|
||||
args: ["A-Za-z0-9_.", true, false]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*(?:[A-Z\\d._]{4}){5,}(?:[A-Z\\d._]{2}--|[A-Z\\d._]{3}-)?\\s*$",
|
||||
flags: "i",
|
||||
args: ["A-Za-z0-9._-", true]
|
||||
args: ["A-Za-z0-9._-", true, false]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$",
|
||||
flags: "i",
|
||||
args: ["0-9a-zA-Z+/=", true]
|
||||
args: ["0-9a-zA-Z+/=", true, false]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$",
|
||||
flags: "i",
|
||||
args: ["0-9A-Za-z+/=", true]
|
||||
args: ["0-9A-Za-z+/=", true, false]
|
||||
},
|
||||
{
|
||||
pattern: "^[ !\"#$%&'()*+,\\-./\\d:;<=>?@A-Z[\\\\\\]^_]{20,}$",
|
||||
flags: "",
|
||||
args: [" -_", false]
|
||||
args: [" -_", false, false]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*[A-Z\\d+\\-]{20,}\\s*$",
|
||||
flags: "i",
|
||||
args: ["+\\-0-9A-Za-z", true]
|
||||
args: ["+\\-0-9A-Za-z", true, false]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*[!\"#$%&'()*+,\\-0-689@A-NP-VX-Z[`a-fh-mp-r]{20,}\\s*$",
|
||||
flags: "",
|
||||
args: ["!-,-0-689@A-NP-VX-Z[`a-fh-mp-r", true]
|
||||
args: ["!-,-0-689@A-NP-VX-Z[`a-fh-mp-r", true, false]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*(?:[N-ZA-M\\d+/]{4}){5,}(?:[N-ZA-M\\d+/]{2}==|[N-ZA-M\\d+/]{3}=)?\\s*$",
|
||||
flags: "i",
|
||||
args: ["N-ZA-Mn-za-m0-9+/=", true]
|
||||
args: ["N-ZA-Mn-za-m0-9+/=", true, false]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*[A-Z\\d./]{20,}\\s*$",
|
||||
flags: "i",
|
||||
args: ["./0-9A-Za-z", true]
|
||||
args: ["./0-9A-Za-z", true, false]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}CC|[A-Z=\\d\\+/]{3}C)?\\s*$",
|
||||
flags: "i",
|
||||
args: ["/128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC", true]
|
||||
args: ["/128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC", true, false]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}55|[A-Z=\\d\\+/]{3}5)?\\s*$",
|
||||
flags: "i",
|
||||
args: ["3GHIJKLMNOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5", true]
|
||||
args: ["3GHIJKLMNOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5", true, false]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}22|[A-Z=\\d\\+/]{3}2)?\\s*$",
|
||||
flags: "i",
|
||||
args: ["ZKj9n+yf0wDVX1s/5YbdxSo=ILaUpPBCHg8uvNO4klm6iJGhQ7eFrWczAMEq3RTt2", true]
|
||||
args: ["ZKj9n+yf0wDVX1s/5YbdxSo=ILaUpPBCHg8uvNO4klm6iJGhQ7eFrWczAMEq3RTt2", true, false]
|
||||
},
|
||||
{
|
||||
pattern: "^\\s*(?:[A-Z=\\d\\+/]{4}){5,}(?:[A-Z=\\d\\+/]{2}55|[A-Z=\\d\\+/]{3}5)?\\s*$",
|
||||
flags: "i",
|
||||
args: ["HNO4klm6ij9n+J2hyf0gzA8uvwDEq3X1Q7ZKeFrWcVTts/MRGYbdxSo=ILaUpPBC5", true]
|
||||
args: ["HNO4klm6ij9n+J2hyf0gzA8uvwDEq3X1Q7ZKeFrWcVTts/MRGYbdxSo=ILaUpPBC5", true, false]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -131,9 +136,9 @@ class FromBase64 extends Operation {
|
|||
* @returns {byteArray}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [alphabet, removeNonAlphChars] = args;
|
||||
const [alphabet, removeNonAlphChars, strictMode] = args;
|
||||
|
||||
return fromBase64(input, alphabet, "byteArray", removeNonAlphChars);
|
||||
return fromBase64(input, alphabet, "byteArray", removeNonAlphChars, strictMode);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import {alphabetName, ALPHABET_OPTIONS} from "../lib/Base85.mjs";
|
||||
import {ALPHABET_OPTIONS} from "../lib/Base85.mjs";
|
||||
|
||||
/**
|
||||
* From Base85 operation
|
||||
|
@ -32,6 +32,46 @@ class FromBase85 extends Operation {
|
|||
type: "editableOption",
|
||||
value: ALPHABET_OPTIONS
|
||||
},
|
||||
{
|
||||
name: "Remove non-alphabet chars",
|
||||
type: "boolean",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: "All-zero group char",
|
||||
type: "binaryShortString",
|
||||
value: "z",
|
||||
maxLength: 1
|
||||
}
|
||||
];
|
||||
this.checks = [
|
||||
{
|
||||
pattern:
|
||||
"^\\s*(?:<~)?" + // Optional whitespace and starting marker
|
||||
"[\\s!-uz]*" + // Any amount of base85 characters and whitespace
|
||||
"[!-uz]{15}" + // At least 15 continoues base85 characters without whitespace
|
||||
"[\\s!-uz]*" + // Any amount of base85 characters and whitespace
|
||||
"(?:~>)?\\s*$", // Optional ending marker and whitespace
|
||||
args: ["!-u"],
|
||||
},
|
||||
{
|
||||
pattern:
|
||||
"^" +
|
||||
"[\\s0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]*" +
|
||||
"[0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]{15}" + // At least 15 continoues base85 characters without whitespace
|
||||
"[\\s0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]*" +
|
||||
"$",
|
||||
args: ["0-9a-zA-Z.\\-:+=^!/*?&<>()[]{}@%$#"],
|
||||
},
|
||||
{
|
||||
pattern:
|
||||
"^" +
|
||||
"[\\s0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]*" +
|
||||
"[0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]{15}" + // At least 15 continoues base85 characters without whitespace
|
||||
"[\\s0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]*" +
|
||||
"$",
|
||||
args: ["0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~"],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -42,7 +82,8 @@ class FromBase85 extends Operation {
|
|||
*/
|
||||
run(input, args) {
|
||||
const alphabet = Utils.expandAlphRange(args[0]).join(""),
|
||||
encoding = alphabetName(alphabet),
|
||||
removeNonAlphChars = args[1],
|
||||
allZeroGroupChar = typeof args[2] === "string" ? args[2].slice(0, 1) : "",
|
||||
result = [];
|
||||
|
||||
if (alphabet.length !== 85 ||
|
||||
|
@ -50,15 +91,29 @@ class FromBase85 extends Operation {
|
|||
throw new OperationError("Alphabet must be of length 85");
|
||||
}
|
||||
|
||||
if (input.length === 0) return [];
|
||||
if (allZeroGroupChar && alphabet.includes(allZeroGroupChar)) {
|
||||
throw new OperationError("The all-zero group char cannot appear in the alphabet");
|
||||
}
|
||||
|
||||
const matches = input.match(/<~(.+?)~>/);
|
||||
// Remove delimiters if present
|
||||
const matches = input.match(/^<~(.+?)~>$/);
|
||||
if (matches !== null) input = matches[1];
|
||||
|
||||
// Remove non-alphabet characters
|
||||
if (removeNonAlphChars) {
|
||||
const re = new RegExp("[^~" + allZeroGroupChar +alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g");
|
||||
input = input.replace(re, "");
|
||||
// Remove delimiters again if present (incase of non-alphabet characters in front/behind delimiters)
|
||||
const matches = input.match(/^<~(.+?)~>$/);
|
||||
if (matches !== null) input = matches[1];
|
||||
}
|
||||
|
||||
if (input.length === 0) return [];
|
||||
|
||||
let i = 0;
|
||||
let block, blockBytes;
|
||||
while (i < input.length) {
|
||||
if (encoding === "Standard" && input[i] === "z") {
|
||||
if (input[i] === allZeroGroupChar) {
|
||||
result.push(0, 0, 0, 0);
|
||||
i++;
|
||||
} else {
|
||||
|
@ -68,8 +123,8 @@ class FromBase85 extends Operation {
|
|||
.split("")
|
||||
.map((chr, idx) => {
|
||||
const digit = alphabet.indexOf(chr);
|
||||
if (digit < 0 || digit > 84) {
|
||||
throw `Invalid character '${chr}' at index ${idx}`;
|
||||
if ((digit < 0 || digit > 84) && chr !== allZeroGroupChar) {
|
||||
throw `Invalid character '${chr}' at index ${i + idx}`;
|
||||
}
|
||||
return digit;
|
||||
});
|
||||
|
|
55
src/core/operations/FromBase92.mjs
Normal file
55
src/core/operations/FromBase92.mjs
Normal file
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* @author sg5506844 [sg5506844@gmail.com]
|
||||
* @copyright Crown Copyright 2021
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import { base92Ord } from "../lib/Base92.mjs";
|
||||
import Operation from "../Operation.mjs";
|
||||
|
||||
/**
|
||||
* From Base92 operation
|
||||
*/
|
||||
class FromBase92 extends Operation {
|
||||
/**
|
||||
* FromBase92 constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "From Base92";
|
||||
this.module = "Default";
|
||||
this.description = "Base92 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems";
|
||||
this.inputType = "string";
|
||||
this.outputType = "byteArray";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
run(input, args) {
|
||||
const res = [];
|
||||
let bitString = "";
|
||||
|
||||
for (let i = 0; i < input.length; i += 2) {
|
||||
if (i + 1 !== input.length) {
|
||||
const x = base92Ord(input[i]) * 91 + base92Ord(input[i + 1]);
|
||||
bitString += x.toString(2).padStart(13, "0");
|
||||
} else {
|
||||
const x = base92Ord(input[i]);
|
||||
bitString += x.toString(2).padStart(6, "0");
|
||||
}
|
||||
while (bitString.length >= 8) {
|
||||
res.push(parseInt(bitString.slice(0, 8), 2));
|
||||
bitString = bitString.slice(8);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
export default FromBase92;
|
|
@ -26,7 +26,7 @@ class FromCharcode extends Operation {
|
|||
this.description = "Converts unicode character codes back into text.<br><br>e.g. <code>0393 03b5 03b9 03ac 20 03c3 03bf 03c5</code> becomes <code>Γειά σου</code>";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Plane_(Unicode)";
|
||||
this.inputType = "string";
|
||||
this.outputType = "byteArray";
|
||||
this.outputType = "ArrayBuffer";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Delimiter",
|
||||
|
@ -44,7 +44,7 @@ class FromCharcode extends Operation {
|
|||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
* @returns {ArrayBuffer}
|
||||
*
|
||||
* @throws {OperationError} if base out of range
|
||||
*/
|
||||
|
@ -59,7 +59,7 @@ class FromCharcode extends Operation {
|
|||
}
|
||||
|
||||
if (input.length === 0) {
|
||||
return [];
|
||||
return new ArrayBuffer;
|
||||
}
|
||||
|
||||
if (base !== 16 && isWorkerEnvironment()) self.setOption("attemptHighlight", false);
|
||||
|
@ -77,7 +77,7 @@ class FromCharcode extends Operation {
|
|||
for (i = 0; i < bites.length; i++) {
|
||||
latin1 += Utils.chr(parseInt(bites[i], base));
|
||||
}
|
||||
return Utils.strToByteArray(latin1);
|
||||
return Utils.strToArrayBuffer(latin1);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
78
src/core/operations/FromFloat.mjs
Normal file
78
src/core/operations/FromFloat.mjs
Normal file
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
* @author tcode2k16 [tcode2k16@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import ieee754 from "ieee754";
|
||||
import {DELIM_OPTIONS} from "../lib/Delim.mjs";
|
||||
|
||||
/**
|
||||
* From Float operation
|
||||
*/
|
||||
class FromFloat extends Operation {
|
||||
|
||||
/**
|
||||
* FromFloat constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "From Float";
|
||||
this.module = "Default";
|
||||
this.description = "Convert from IEEE754 Floating Point Numbers";
|
||||
this.infoURL = "https://wikipedia.org/wiki/IEEE_754";
|
||||
this.inputType = "string";
|
||||
this.outputType = "byteArray";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Endianness",
|
||||
"type": "option",
|
||||
"value": [
|
||||
"Big Endian",
|
||||
"Little Endian"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Size",
|
||||
"type": "option",
|
||||
"value": [
|
||||
"Float (4 bytes)",
|
||||
"Double (8 bytes)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Delimiter",
|
||||
"type": "option",
|
||||
"value": DELIM_OPTIONS
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {byteArray}
|
||||
*/
|
||||
run(input, args) {
|
||||
if (input.length === 0) return [];
|
||||
|
||||
const [endianness, size, delimiterName] = args;
|
||||
const delim = Utils.charRep(delimiterName || "Space");
|
||||
const byteSize = size === "Double (8 bytes)" ? 8 : 4;
|
||||
const isLE = endianness === "Little Endian";
|
||||
const mLen = byteSize === 4 ? 23 : 52;
|
||||
const floats = input.split(delim);
|
||||
|
||||
const output = new Array(floats.length*byteSize);
|
||||
for (let i = 0; i < floats.length; i++) {
|
||||
ieee754.write(output, parseFloat(floats[i]), i*byteSize, isLE, mLen, byteSize);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default FromFloat;
|
151
src/core/operations/GOSTDecrypt.mjs
Normal file
151
src/core/operations/GOSTDecrypt.mjs
Normal file
|
@ -0,0 +1,151 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import { toHexFast, fromHex } from "../lib/Hex.mjs";
|
||||
import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js";
|
||||
|
||||
/**
|
||||
* GOST Decrypt operation
|
||||
*/
|
||||
class GOSTDecrypt extends Operation {
|
||||
|
||||
/**
|
||||
* GOSTDecrypt constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "GOST Decrypt";
|
||||
this.module = "Ciphers";
|
||||
this.description = "The GOST block cipher (Magma), defined in the standard GOST 28147-89 (RFC 5830), is a Soviet and Russian government standard symmetric key block cipher with a block size of 64 bits. The original standard, published in 1989, did not give the cipher any name, but the most recent revision of the standard, GOST R 34.12-2015 (RFC 7801, RFC 8891), specifies that it may be referred to as Magma. The GOST hash function is based on this cipher. The new standard also specifies a new 128-bit block cipher called Kuznyechik.<br><br>Developed in the 1970s, the standard had been marked 'Top Secret' and then downgraded to 'Secret' in 1990. Shortly after the dissolution of the USSR, it was declassified and it was released to the public in 1994. GOST 28147 was a Soviet alternative to the United States standard algorithm, DES. Thus, the two are very similar in structure.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Key",
|
||||
type: "toggleString",
|
||||
value: "",
|
||||
toggleValues: ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
name: "IV",
|
||||
type: "toggleString",
|
||||
value: "",
|
||||
toggleValues: ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
name: "Input type",
|
||||
type: "option",
|
||||
value: ["Hex", "Raw"]
|
||||
},
|
||||
{
|
||||
name: "Output type",
|
||||
type: "option",
|
||||
value: ["Raw", "Hex"]
|
||||
},
|
||||
{
|
||||
name: "Algorithm",
|
||||
type: "argSelector",
|
||||
value: [
|
||||
{
|
||||
name: "GOST 28147 (1989)",
|
||||
on: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Magma, 2015)",
|
||||
off: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Kuznyechik, 2015)",
|
||||
off: [5]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "sBox",
|
||||
type: "option",
|
||||
value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"]
|
||||
},
|
||||
{
|
||||
name: "Block mode",
|
||||
type: "option",
|
||||
value: ["ECB", "CFB", "OFB", "CTR", "CBC"]
|
||||
},
|
||||
{
|
||||
name: "Key meshing mode",
|
||||
type: "option",
|
||||
value: ["NO", "CP"]
|
||||
},
|
||||
{
|
||||
name: "Padding",
|
||||
type: "option",
|
||||
value: ["NO", "PKCS5", "ZERO", "RANDOM", "BIT"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [keyObj, ivObj, inputType, outputType, version, sBox, blockMode, keyMeshing, padding] = args;
|
||||
|
||||
const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option));
|
||||
const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option));
|
||||
input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input));
|
||||
|
||||
let blockLength, versionNum;
|
||||
switch (version) {
|
||||
case "GOST 28147 (1989)":
|
||||
versionNum = 1989;
|
||||
blockLength = 64;
|
||||
break;
|
||||
case "GOST R 34.12 (Magma, 2015)":
|
||||
versionNum = 2015;
|
||||
blockLength = 64;
|
||||
break;
|
||||
case "GOST R 34.12 (Kuznyechik, 2015)":
|
||||
versionNum = 2015;
|
||||
blockLength = 128;
|
||||
break;
|
||||
default:
|
||||
throw new OperationError(`Unknown algorithm version: ${version}`);
|
||||
}
|
||||
|
||||
const sBoxVal = versionNum === 1989 ? sBox : null;
|
||||
|
||||
const algorithm = {
|
||||
version: versionNum,
|
||||
length: blockLength,
|
||||
mode: "ES",
|
||||
sBox: sBoxVal,
|
||||
block: blockMode,
|
||||
keyMeshing: keyMeshing,
|
||||
padding: padding
|
||||
};
|
||||
|
||||
try {
|
||||
const Hex = CryptoGost.coding.Hex;
|
||||
if (iv) algorithm.iv = Hex.decode(iv);
|
||||
|
||||
const cipher = GostEngine.getGostCipher(algorithm);
|
||||
const out = Hex.encode(cipher.decrypt(Hex.decode(key), Hex.decode(input)));
|
||||
|
||||
return outputType === "Hex" ? out : Utils.byteArrayToChars(fromHex(out));
|
||||
} catch (err) {
|
||||
throw new OperationError(err);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default GOSTDecrypt;
|
151
src/core/operations/GOSTEncrypt.mjs
Normal file
151
src/core/operations/GOSTEncrypt.mjs
Normal file
|
@ -0,0 +1,151 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import { toHexFast, fromHex } from "../lib/Hex.mjs";
|
||||
import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js";
|
||||
|
||||
/**
|
||||
* GOST Encrypt operation
|
||||
*/
|
||||
class GOSTEncrypt extends Operation {
|
||||
|
||||
/**
|
||||
* GOSTEncrypt constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "GOST Encrypt";
|
||||
this.module = "Ciphers";
|
||||
this.description = "The GOST block cipher (Magma), defined in the standard GOST 28147-89 (RFC 5830), is a Soviet and Russian government standard symmetric key block cipher with a block size of 64 bits. The original standard, published in 1989, did not give the cipher any name, but the most recent revision of the standard, GOST R 34.12-2015 (RFC 7801, RFC 8891), specifies that it may be referred to as Magma. The GOST hash function is based on this cipher. The new standard also specifies a new 128-bit block cipher called Kuznyechik.<br><br>Developed in the 1970s, the standard had been marked 'Top Secret' and then downgraded to 'Secret' in 1990. Shortly after the dissolution of the USSR, it was declassified and it was released to the public in 1994. GOST 28147 was a Soviet alternative to the United States standard algorithm, DES. Thus, the two are very similar in structure.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Key",
|
||||
type: "toggleString",
|
||||
value: "",
|
||||
toggleValues: ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
name: "IV",
|
||||
type: "toggleString",
|
||||
value: "",
|
||||
toggleValues: ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
name: "Input type",
|
||||
type: "option",
|
||||
value: ["Raw", "Hex"]
|
||||
},
|
||||
{
|
||||
name: "Output type",
|
||||
type: "option",
|
||||
value: ["Hex", "Raw"]
|
||||
},
|
||||
{
|
||||
name: "Algorithm",
|
||||
type: "argSelector",
|
||||
value: [
|
||||
{
|
||||
name: "GOST 28147 (1989)",
|
||||
on: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Magma, 2015)",
|
||||
off: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Kuznyechik, 2015)",
|
||||
off: [5]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "sBox",
|
||||
type: "option",
|
||||
value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"]
|
||||
},
|
||||
{
|
||||
name: "Block mode",
|
||||
type: "option",
|
||||
value: ["ECB", "CFB", "OFB", "CTR", "CBC"]
|
||||
},
|
||||
{
|
||||
name: "Key meshing mode",
|
||||
type: "option",
|
||||
value: ["NO", "CP"]
|
||||
},
|
||||
{
|
||||
name: "Padding",
|
||||
type: "option",
|
||||
value: ["NO", "PKCS5", "ZERO", "RANDOM", "BIT"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [keyObj, ivObj, inputType, outputType, version, sBox, blockMode, keyMeshing, padding] = args;
|
||||
|
||||
const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option));
|
||||
const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option));
|
||||
input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input));
|
||||
|
||||
let blockLength, versionNum;
|
||||
switch (version) {
|
||||
case "GOST 28147 (1989)":
|
||||
versionNum = 1989;
|
||||
blockLength = 64;
|
||||
break;
|
||||
case "GOST R 34.12 (Magma, 2015)":
|
||||
versionNum = 2015;
|
||||
blockLength = 64;
|
||||
break;
|
||||
case "GOST R 34.12 (Kuznyechik, 2015)":
|
||||
versionNum = 2015;
|
||||
blockLength = 128;
|
||||
break;
|
||||
default:
|
||||
throw new OperationError(`Unknown algorithm version: ${version}`);
|
||||
}
|
||||
|
||||
const sBoxVal = versionNum === 1989 ? sBox : null;
|
||||
|
||||
const algorithm = {
|
||||
version: versionNum,
|
||||
length: blockLength,
|
||||
mode: "ES",
|
||||
sBox: sBoxVal,
|
||||
block: blockMode,
|
||||
keyMeshing: keyMeshing,
|
||||
padding: padding
|
||||
};
|
||||
|
||||
try {
|
||||
const Hex = CryptoGost.coding.Hex;
|
||||
if (iv) algorithm.iv = Hex.decode(iv);
|
||||
|
||||
const cipher = GostEngine.getGostCipher(algorithm);
|
||||
const out = Hex.encode(cipher.encrypt(Hex.decode(key), Hex.decode(input)));
|
||||
|
||||
return outputType === "Hex" ? out : Utils.byteArrayToChars(fromHex(out));
|
||||
} catch (err) {
|
||||
throw new OperationError(err);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default GOSTEncrypt;
|
|
@ -7,7 +7,7 @@
|
|||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import GostDigest from "../vendor/gost/gostDigest.mjs";
|
||||
import {toHexFast} from "../lib/Hex.mjs";
|
||||
import { toHexFast } from "../lib/Hex.mjs";
|
||||
|
||||
/**
|
||||
* GOST hash operation
|
||||
|
@ -20,7 +20,7 @@ class GOSTHash extends Operation {
|
|||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "GOST hash";
|
||||
this.name = "GOST Hash";
|
||||
this.module = "Hashing";
|
||||
this.description = "The GOST hash function, defined in the standards GOST R 34.11-94 and GOST 34.311-95 is a 256-bit cryptographic hash function. It was initially defined in the Russian national standard GOST R 34.11-94 <i>Information Technology – Cryptographic Information Security – Hash Function</i>. The equivalent standard used by other member-states of the CIS is GOST 34.311-95.<br><br>This function must not be confused with a different Streebog hash function, which is defined in the new revision of the standard GOST R 34.11-2012.<br><br>The GOST hash function is based on the GOST block cipher.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/GOST_(hash_function)";
|
||||
|
@ -28,20 +28,30 @@ class GOSTHash extends Operation {
|
|||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "S-Box",
|
||||
"type": "option",
|
||||
"value": [
|
||||
"D-A",
|
||||
"D-SC",
|
||||
"E-TEST",
|
||||
"E-A",
|
||||
"E-B",
|
||||
"E-C",
|
||||
"E-D",
|
||||
"E-SC",
|
||||
"E-Z",
|
||||
"D-TEST"
|
||||
name: "Algorithm",
|
||||
type: "argSelector",
|
||||
value: [
|
||||
{
|
||||
name: "GOST 28147 (1994)",
|
||||
off: [1],
|
||||
on: [2]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.11 (Streebog, 2012)",
|
||||
on: [1],
|
||||
off: [2]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Digest length",
|
||||
type: "option",
|
||||
value: ["256", "512"]
|
||||
},
|
||||
{
|
||||
name: "sBox",
|
||||
type: "option",
|
||||
value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -52,13 +62,23 @@ class GOSTHash extends Operation {
|
|||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [version, length, sBox] = args;
|
||||
|
||||
const versionNum = version === "GOST 28147 (1994)" ? 1994 : 2012;
|
||||
const algorithm = {
|
||||
name: versionNum === 1994 ? "GOST 28147" : "GOST R 34.10",
|
||||
version: versionNum,
|
||||
mode: "HASH"
|
||||
};
|
||||
|
||||
if (versionNum === 1994) {
|
||||
algorithm.sBox = sBox;
|
||||
} else {
|
||||
algorithm.length = parseInt(length, 10);
|
||||
}
|
||||
|
||||
try {
|
||||
const sBox = args[1];
|
||||
const gostDigest = new GostDigest({
|
||||
name: "GOST R 34.11",
|
||||
version: 1994,
|
||||
sBox: sBox
|
||||
});
|
||||
const gostDigest = new GostDigest(algorithm);
|
||||
|
||||
return toHexFast(gostDigest.digest(input));
|
||||
} catch (err) {
|
||||
|
|
142
src/core/operations/GOSTKeyUnwrap.mjs
Normal file
142
src/core/operations/GOSTKeyUnwrap.mjs
Normal file
|
@ -0,0 +1,142 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import { toHexFast, fromHex } from "../lib/Hex.mjs";
|
||||
import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js";
|
||||
|
||||
/**
|
||||
* GOST Key Unwrap operation
|
||||
*/
|
||||
class GOSTKeyUnwrap extends Operation {
|
||||
|
||||
/**
|
||||
* GOSTKeyUnwrap constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "GOST Key Unwrap";
|
||||
this.module = "Ciphers";
|
||||
this.description = "A decryptor for keys wrapped using one of the GOST block ciphers.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Key",
|
||||
type: "toggleString",
|
||||
value: "",
|
||||
toggleValues: ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
name: "User Key Material",
|
||||
type: "toggleString",
|
||||
value: "",
|
||||
toggleValues: ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
name: "Input type",
|
||||
type: "option",
|
||||
value: ["Hex", "Raw"]
|
||||
},
|
||||
{
|
||||
name: "Output type",
|
||||
type: "option",
|
||||
value: ["Raw", "Hex"]
|
||||
},
|
||||
{
|
||||
name: "Algorithm",
|
||||
type: "argSelector",
|
||||
value: [
|
||||
{
|
||||
name: "GOST 28147 (1989)",
|
||||
on: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Magma, 2015)",
|
||||
off: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Kuznyechik, 2015)",
|
||||
off: [5]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "sBox",
|
||||
type: "option",
|
||||
value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"]
|
||||
},
|
||||
{
|
||||
name: "Key wrapping",
|
||||
type: "option",
|
||||
value: ["NO", "CP", "SC"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [keyObj, ukmObj, inputType, outputType, version, sBox, keyWrapping] = args;
|
||||
|
||||
const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option));
|
||||
const ukm = toHexFast(Utils.convertToByteArray(ukmObj.string, ukmObj.option));
|
||||
input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input));
|
||||
|
||||
let blockLength, versionNum;
|
||||
switch (version) {
|
||||
case "GOST 28147 (1989)":
|
||||
versionNum = 1989;
|
||||
blockLength = 64;
|
||||
break;
|
||||
case "GOST R 34.12 (Magma, 2015)":
|
||||
versionNum = 2015;
|
||||
blockLength = 64;
|
||||
break;
|
||||
case "GOST R 34.12 (Kuznyechik, 2015)":
|
||||
versionNum = 2015;
|
||||
blockLength = 128;
|
||||
break;
|
||||
default:
|
||||
throw new OperationError(`Unknown algorithm version: ${version}`);
|
||||
}
|
||||
|
||||
const sBoxVal = versionNum === 1989 ? sBox : null;
|
||||
|
||||
const algorithm = {
|
||||
version: versionNum,
|
||||
length: blockLength,
|
||||
mode: "KW",
|
||||
sBox: sBoxVal,
|
||||
keyWrapping: keyWrapping
|
||||
};
|
||||
|
||||
try {
|
||||
const Hex = CryptoGost.coding.Hex;
|
||||
algorithm.ukm = Hex.decode(ukm);
|
||||
|
||||
const cipher = GostEngine.getGostCipher(algorithm);
|
||||
const out = Hex.encode(cipher.unwrapKey(Hex.decode(key), Hex.decode(input)));
|
||||
|
||||
return outputType === "Hex" ? out : Utils.byteArrayToChars(fromHex(out));
|
||||
} catch (err) {
|
||||
if (err.toString().includes("Invalid typed array length")) {
|
||||
throw new OperationError("Incorrect input length. Must be a multiple of the block size.");
|
||||
}
|
||||
throw new OperationError(err);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default GOSTKeyUnwrap;
|
142
src/core/operations/GOSTKeyWrap.mjs
Normal file
142
src/core/operations/GOSTKeyWrap.mjs
Normal file
|
@ -0,0 +1,142 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import { toHexFast, fromHex } from "../lib/Hex.mjs";
|
||||
import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js";
|
||||
|
||||
/**
|
||||
* GOST Key Wrap operation
|
||||
*/
|
||||
class GOSTKeyWrap extends Operation {
|
||||
|
||||
/**
|
||||
* GOSTKeyWrap constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "GOST Key Wrap";
|
||||
this.module = "Ciphers";
|
||||
this.description = "A key wrapping algorithm for protecting keys in untrusted storage using one of the GOST block cipers.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Key",
|
||||
type: "toggleString",
|
||||
value: "",
|
||||
toggleValues: ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
name: "User Key Material",
|
||||
type: "toggleString",
|
||||
value: "",
|
||||
toggleValues: ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
name: "Input type",
|
||||
type: "option",
|
||||
value: ["Raw", "Hex"]
|
||||
},
|
||||
{
|
||||
name: "Output type",
|
||||
type: "option",
|
||||
value: ["Hex", "Raw"]
|
||||
},
|
||||
{
|
||||
name: "Algorithm",
|
||||
type: "argSelector",
|
||||
value: [
|
||||
{
|
||||
name: "GOST 28147 (1989)",
|
||||
on: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Magma, 2015)",
|
||||
off: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Kuznyechik, 2015)",
|
||||
off: [5]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "sBox",
|
||||
type: "option",
|
||||
value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"]
|
||||
},
|
||||
{
|
||||
name: "Key wrapping",
|
||||
type: "option",
|
||||
value: ["NO", "CP", "SC"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [keyObj, ukmObj, inputType, outputType, version, sBox, keyWrapping] = args;
|
||||
|
||||
const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option));
|
||||
const ukm = toHexFast(Utils.convertToByteArray(ukmObj.string, ukmObj.option));
|
||||
input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input));
|
||||
|
||||
let blockLength, versionNum;
|
||||
switch (version) {
|
||||
case "GOST 28147 (1989)":
|
||||
versionNum = 1989;
|
||||
blockLength = 64;
|
||||
break;
|
||||
case "GOST R 34.12 (Magma, 2015)":
|
||||
versionNum = 2015;
|
||||
blockLength = 64;
|
||||
break;
|
||||
case "GOST R 34.12 (Kuznyechik, 2015)":
|
||||
versionNum = 2015;
|
||||
blockLength = 128;
|
||||
break;
|
||||
default:
|
||||
throw new OperationError(`Unknown algorithm version: ${version}`);
|
||||
}
|
||||
|
||||
const sBoxVal = versionNum === 1989 ? sBox : null;
|
||||
|
||||
const algorithm = {
|
||||
version: versionNum,
|
||||
length: blockLength,
|
||||
mode: "KW",
|
||||
sBox: sBoxVal,
|
||||
keyWrapping: keyWrapping
|
||||
};
|
||||
|
||||
try {
|
||||
const Hex = CryptoGost.coding.Hex;
|
||||
algorithm.ukm = Hex.decode(ukm);
|
||||
|
||||
const cipher = GostEngine.getGostCipher(algorithm);
|
||||
const out = Hex.encode(cipher.wrapKey(Hex.decode(key), Hex.decode(input)));
|
||||
|
||||
return outputType === "Hex" ? out : Utils.byteArrayToChars(fromHex(out));
|
||||
} catch (err) {
|
||||
if (err.toString().includes("Invalid typed array length")) {
|
||||
throw new OperationError("Incorrect input length. Must be a multiple of the block size.");
|
||||
}
|
||||
throw new OperationError(err);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default GOSTKeyWrap;
|
142
src/core/operations/GOSTSign.mjs
Normal file
142
src/core/operations/GOSTSign.mjs
Normal file
|
@ -0,0 +1,142 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import { toHexFast, fromHex } from "../lib/Hex.mjs";
|
||||
import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js";
|
||||
|
||||
/**
|
||||
* GOST Sign operation
|
||||
*/
|
||||
class GOSTSign extends Operation {
|
||||
|
||||
/**
|
||||
* GOSTSign constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "GOST Sign";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Sign a plaintext message using one of the GOST block ciphers.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Key",
|
||||
type: "toggleString",
|
||||
value: "",
|
||||
toggleValues: ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
name: "IV",
|
||||
type: "toggleString",
|
||||
value: "",
|
||||
toggleValues: ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
name: "Input type",
|
||||
type: "option",
|
||||
value: ["Raw", "Hex"]
|
||||
},
|
||||
{
|
||||
name: "Output type",
|
||||
type: "option",
|
||||
value: ["Hex", "Raw"]
|
||||
},
|
||||
{
|
||||
name: "Algorithm",
|
||||
type: "argSelector",
|
||||
value: [
|
||||
{
|
||||
name: "GOST 28147 (1989)",
|
||||
on: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Magma, 2015)",
|
||||
off: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Kuznyechik, 2015)",
|
||||
off: [5]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "sBox",
|
||||
type: "option",
|
||||
value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"]
|
||||
},
|
||||
{
|
||||
name: "MAC length",
|
||||
type: "number",
|
||||
value: 32,
|
||||
min: 8,
|
||||
max: 64,
|
||||
step: 8
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [keyObj, ivObj, inputType, outputType, version, sBox, macLength] = args;
|
||||
|
||||
const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option));
|
||||
const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option));
|
||||
input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input));
|
||||
|
||||
let blockLength, versionNum;
|
||||
switch (version) {
|
||||
case "GOST 28147 (1989)":
|
||||
versionNum = 1989;
|
||||
blockLength = 64;
|
||||
break;
|
||||
case "GOST R 34.12 (Magma, 2015)":
|
||||
versionNum = 2015;
|
||||
blockLength = 64;
|
||||
break;
|
||||
case "GOST R 34.12 (Kuznyechik, 2015)":
|
||||
versionNum = 2015;
|
||||
blockLength = 128;
|
||||
break;
|
||||
default:
|
||||
throw new OperationError(`Unknown algorithm version: ${version}`);
|
||||
}
|
||||
|
||||
const sBoxVal = versionNum === 1989 ? sBox : null;
|
||||
|
||||
const algorithm = {
|
||||
version: versionNum,
|
||||
length: blockLength,
|
||||
mode: "MAC",
|
||||
sBox: sBoxVal,
|
||||
macLength: macLength
|
||||
};
|
||||
|
||||
try {
|
||||
const Hex = CryptoGost.coding.Hex;
|
||||
if (iv) algorithm.iv = Hex.decode(iv);
|
||||
|
||||
const cipher = GostEngine.getGostCipher(algorithm);
|
||||
const out = Hex.encode(cipher.sign(Hex.decode(key), Hex.decode(input)));
|
||||
|
||||
return outputType === "Hex" ? out : Utils.byteArrayToChars(fromHex(out));
|
||||
} catch (err) {
|
||||
throw new OperationError(err);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default GOSTSign;
|
136
src/core/operations/GOSTVerify.mjs
Normal file
136
src/core/operations/GOSTVerify.mjs
Normal file
|
@ -0,0 +1,136 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import { toHexFast } from "../lib/Hex.mjs";
|
||||
import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js";
|
||||
|
||||
/**
|
||||
* GOST Verify operation
|
||||
*/
|
||||
class GOSTVerify extends Operation {
|
||||
|
||||
/**
|
||||
* GOSTVerify constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "GOST Verify";
|
||||
this.module = "Ciphers";
|
||||
this.description = "Verify the signature of a plaintext message using one of the GOST block ciphers. Enter the signature in the MAC field.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Key",
|
||||
type: "toggleString",
|
||||
value: "",
|
||||
toggleValues: ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
name: "IV",
|
||||
type: "toggleString",
|
||||
value: "",
|
||||
toggleValues: ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
name: "MAC",
|
||||
type: "toggleString",
|
||||
value: "",
|
||||
toggleValues: ["Hex", "UTF8", "Latin1", "Base64"]
|
||||
},
|
||||
{
|
||||
name: "Input type",
|
||||
type: "option",
|
||||
value: ["Raw", "Hex"]
|
||||
},
|
||||
{
|
||||
name: "Algorithm",
|
||||
type: "argSelector",
|
||||
value: [
|
||||
{
|
||||
name: "GOST 28147 (1989)",
|
||||
on: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Magma, 2015)",
|
||||
off: [5]
|
||||
},
|
||||
{
|
||||
name: "GOST R 34.12 (Kuznyechik, 2015)",
|
||||
off: [5]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "sBox",
|
||||
type: "option",
|
||||
value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [keyObj, ivObj, macObj, inputType, version, sBox] = args;
|
||||
|
||||
const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option));
|
||||
const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option));
|
||||
const mac = toHexFast(Utils.convertToByteArray(macObj.string, macObj.option));
|
||||
input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input));
|
||||
|
||||
let blockLength, versionNum;
|
||||
switch (version) {
|
||||
case "GOST 28147 (1989)":
|
||||
versionNum = 1989;
|
||||
blockLength = 64;
|
||||
break;
|
||||
case "GOST R 34.12 (Magma, 2015)":
|
||||
versionNum = 2015;
|
||||
blockLength = 64;
|
||||
break;
|
||||
case "GOST R 34.12 (Kuznyechik, 2015)":
|
||||
versionNum = 2015;
|
||||
blockLength = 128;
|
||||
break;
|
||||
default:
|
||||
throw new OperationError(`Unknown algorithm version: ${version}`);
|
||||
}
|
||||
|
||||
const sBoxVal = versionNum === 1989 ? sBox : null;
|
||||
|
||||
const algorithm = {
|
||||
version: versionNum,
|
||||
length: blockLength,
|
||||
mode: "MAC",
|
||||
sBox: sBoxVal,
|
||||
macLength: mac.length * 4
|
||||
};
|
||||
|
||||
try {
|
||||
const Hex = CryptoGost.coding.Hex;
|
||||
if (iv) algorithm.iv = Hex.decode(iv);
|
||||
|
||||
const cipher = GostEngine.getGostCipher(algorithm);
|
||||
const out = cipher.verify(Hex.decode(key), Hex.decode(mac), Hex.decode(input));
|
||||
|
||||
return out ? "The signature matches" : "The signature does not match";
|
||||
} catch (err) {
|
||||
throw new OperationError(err);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default GOSTVerify;
|
|
@ -1,5 +1,6 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @author john19696 [john19696@protonmail.com]
|
||||
* @copyright Crown Copyright 2016
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
@ -33,6 +34,9 @@ import BLAKE2b from "./BLAKE2b.mjs";
|
|||
import BLAKE2s from "./BLAKE2s.mjs";
|
||||
import Streebog from "./Streebog.mjs";
|
||||
import GOSTHash from "./GOSTHash.mjs";
|
||||
import LMHash from "./LMHash.mjs";
|
||||
import NTHash from "./NTHash.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* Generate all hashes operation
|
||||
|
@ -51,7 +55,75 @@ class GenerateAllHashes extends Operation {
|
|||
this.infoURL = "https://wikipedia.org/wiki/Comparison_of_cryptographic_hash_functions";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
this.args = [
|
||||
{
|
||||
name: "Length (bits)",
|
||||
type: "option",
|
||||
value: [
|
||||
"All", "128", "160", "224", "256", "320", "384", "512"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Include names",
|
||||
type: "boolean",
|
||||
value: true
|
||||
},
|
||||
];
|
||||
this.hashes = [
|
||||
{name: "MD2", algo: (new MD2()), inputType: "arrayBuffer", params: []},
|
||||
{name: "MD4", algo: (new MD4()), inputType: "arrayBuffer", params: []},
|
||||
{name: "MD5", algo: (new MD5()), inputType: "arrayBuffer", params: []},
|
||||
{name: "MD6", algo: (new MD6()), inputType: "str", params: []},
|
||||
{name: "SHA0", algo: (new SHA0()), inputType: "arrayBuffer", params: []},
|
||||
{name: "SHA1", algo: (new SHA1()), inputType: "arrayBuffer", params: []},
|
||||
{name: "SHA2 224", algo: (new SHA2()), inputType: "arrayBuffer", params: ["224"]},
|
||||
{name: "SHA2 256", algo: (new SHA2()), inputType: "arrayBuffer", params: ["256"]},
|
||||
{name: "SHA2 384", algo: (new SHA2()), inputType: "arrayBuffer", params: ["384"]},
|
||||
{name: "SHA2 512", algo: (new SHA2()), inputType: "arrayBuffer", params: ["512"]},
|
||||
{name: "SHA3 224", algo: (new SHA3()), inputType: "arrayBuffer", params: ["224"]},
|
||||
{name: "SHA3 256", algo: (new SHA3()), inputType: "arrayBuffer", params: ["256"]},
|
||||
{name: "SHA3 384", algo: (new SHA3()), inputType: "arrayBuffer", params: ["384"]},
|
||||
{name: "SHA3 512", algo: (new SHA3()), inputType: "arrayBuffer", params: ["512"]},
|
||||
{name: "Keccak 224", algo: (new Keccak()), inputType: "arrayBuffer", params: ["224"]},
|
||||
{name: "Keccak 256", algo: (new Keccak()), inputType: "arrayBuffer", params: ["256"]},
|
||||
{name: "Keccak 384", algo: (new Keccak()), inputType: "arrayBuffer", params: ["384"]},
|
||||
{name: "Keccak 512", algo: (new Keccak()), inputType: "arrayBuffer", params: ["512"]},
|
||||
{name: "Shake 128", algo: (new Shake()), inputType: "arrayBuffer", params: ["128", 256]},
|
||||
{name: "Shake 256", algo: (new Shake()), inputType: "arrayBuffer", params: ["256", 512]},
|
||||
{name: "RIPEMD-128", algo: (new RIPEMD()), inputType: "arrayBuffer", params: ["128"]},
|
||||
{name: "RIPEMD-160", algo: (new RIPEMD()), inputType: "arrayBuffer", params: ["160"]},
|
||||
{name: "RIPEMD-256", algo: (new RIPEMD()), inputType: "arrayBuffer", params: ["256"]},
|
||||
{name: "RIPEMD-320", algo: (new RIPEMD()), inputType: "arrayBuffer", params: ["320"]},
|
||||
{name: "HAS-160", algo: (new HAS160()), inputType: "arrayBuffer", params: []},
|
||||
{name: "Whirlpool-0", algo: (new Whirlpool()), inputType: "arrayBuffer", params: ["Whirlpool-0"]},
|
||||
{name: "Whirlpool-T", algo: (new Whirlpool()), inputType: "arrayBuffer", params: ["Whirlpool-T"]},
|
||||
{name: "Whirlpool", algo: (new Whirlpool()), inputType: "arrayBuffer", params: ["Whirlpool"]},
|
||||
{name: "BLAKE2b-128", algo: (new BLAKE2b), inputType: "arrayBuffer", params: ["128", "Hex", {string: "", option: "UTF8"}]},
|
||||
{name: "BLAKE2b-160", algo: (new BLAKE2b), inputType: "arrayBuffer", params: ["160", "Hex", {string: "", option: "UTF8"}]},
|
||||
{name: "BLAKE2b-256", algo: (new BLAKE2b), inputType: "arrayBuffer", params: ["256", "Hex", {string: "", option: "UTF8"}]},
|
||||
{name: "BLAKE2b-384", algo: (new BLAKE2b), inputType: "arrayBuffer", params: ["384", "Hex", {string: "", option: "UTF8"}]},
|
||||
{name: "BLAKE2b-512", algo: (new BLAKE2b), inputType: "arrayBuffer", params: ["512", "Hex", {string: "", option: "UTF8"}]},
|
||||
{name: "BLAKE2s-128", algo: (new BLAKE2s), inputType: "arrayBuffer", params: ["128", "Hex", {string: "", option: "UTF8"}]},
|
||||
{name: "BLAKE2s-160", algo: (new BLAKE2s), inputType: "arrayBuffer", params: ["160", "Hex", {string: "", option: "UTF8"}]},
|
||||
{name: "BLAKE2s-256", algo: (new BLAKE2s), inputType: "arrayBuffer", params: ["256", "Hex", {string: "", option: "UTF8"}]},
|
||||
{name: "Streebog-256", algo: (new Streebog), inputType: "arrayBuffer", params: ["256"]},
|
||||
{name: "Streebog-512", algo: (new Streebog), inputType: "arrayBuffer", params: ["512"]},
|
||||
{name: "GOST", algo: (new GOSTHash), inputType: "arrayBuffer", params: ["GOST 28147 (1994)", "256", "D-A"]},
|
||||
{name: "LM Hash", algo: (new LMHash), inputType: "str", params: []},
|
||||
{name: "NT Hash", algo: (new NTHash), inputType: "str", params: []},
|
||||
{name: "SSDEEP", algo: (new SSDEEP()), inputType: "str"},
|
||||
{name: "CTPH", algo: (new CTPH()), inputType: "str"}
|
||||
];
|
||||
this.checksums = [
|
||||
{name: "Fletcher-8", algo: (new Fletcher8Checksum), inputType: "byteArray", params: []},
|
||||
{name: "Fletcher-16", algo: (new Fletcher16Checksum), inputType: "byteArray", params: []},
|
||||
{name: "Fletcher-32", algo: (new Fletcher32Checksum), inputType: "byteArray", params: []},
|
||||
{name: "Fletcher-64", algo: (new Fletcher64Checksum), inputType: "byteArray", params: []},
|
||||
{name: "Adler-32", algo: (new Adler32Checksum), inputType: "byteArray", params: []},
|
||||
{name: "CRC-8", algo: (new CRC8Checksum), inputType: "arrayBuffer", params: ["CRC-8"]},
|
||||
{name: "CRC-16", algo: (new CRC16Checksum), inputType: "arrayBuffer", params: []},
|
||||
{name: "CRC-32", algo: (new CRC32Checksum), inputType: "arrayBuffer", params: []}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,63 +132,74 @@ class GenerateAllHashes extends Operation {
|
|||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const arrayBuffer = input,
|
||||
str = Utils.arrayBufferToStr(arrayBuffer, false),
|
||||
byteArray = new Uint8Array(arrayBuffer),
|
||||
output = "MD2: " + (new MD2()).run(arrayBuffer, []) +
|
||||
"\nMD4: " + (new MD4()).run(arrayBuffer, []) +
|
||||
"\nMD5: " + (new MD5()).run(arrayBuffer, []) +
|
||||
"\nMD6: " + (new MD6()).run(str, []) +
|
||||
"\nSHA0: " + (new SHA0()).run(arrayBuffer, []) +
|
||||
"\nSHA1: " + (new SHA1()).run(arrayBuffer, []) +
|
||||
"\nSHA2 224: " + (new SHA2()).run(arrayBuffer, ["224"]) +
|
||||
"\nSHA2 256: " + (new SHA2()).run(arrayBuffer, ["256"]) +
|
||||
"\nSHA2 384: " + (new SHA2()).run(arrayBuffer, ["384"]) +
|
||||
"\nSHA2 512: " + (new SHA2()).run(arrayBuffer, ["512"]) +
|
||||
"\nSHA3 224: " + (new SHA3()).run(arrayBuffer, ["224"]) +
|
||||
"\nSHA3 256: " + (new SHA3()).run(arrayBuffer, ["256"]) +
|
||||
"\nSHA3 384: " + (new SHA3()).run(arrayBuffer, ["384"]) +
|
||||
"\nSHA3 512: " + (new SHA3()).run(arrayBuffer, ["512"]) +
|
||||
"\nKeccak 224: " + (new Keccak()).run(arrayBuffer, ["224"]) +
|
||||
"\nKeccak 256: " + (new Keccak()).run(arrayBuffer, ["256"]) +
|
||||
"\nKeccak 384: " + (new Keccak()).run(arrayBuffer, ["384"]) +
|
||||
"\nKeccak 512: " + (new Keccak()).run(arrayBuffer, ["512"]) +
|
||||
"\nShake 128: " + (new Shake()).run(arrayBuffer, ["128", 256]) +
|
||||
"\nShake 256: " + (new Shake()).run(arrayBuffer, ["256", 512]) +
|
||||
"\nRIPEMD-128: " + (new RIPEMD()).run(arrayBuffer, ["128"]) +
|
||||
"\nRIPEMD-160: " + (new RIPEMD()).run(arrayBuffer, ["160"]) +
|
||||
"\nRIPEMD-256: " + (new RIPEMD()).run(arrayBuffer, ["256"]) +
|
||||
"\nRIPEMD-320: " + (new RIPEMD()).run(arrayBuffer, ["320"]) +
|
||||
"\nHAS-160: " + (new HAS160()).run(arrayBuffer, []) +
|
||||
"\nWhirlpool-0: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool-0"]) +
|
||||
"\nWhirlpool-T: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool-T"]) +
|
||||
"\nWhirlpool: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool"]) +
|
||||
"\nBLAKE2b-128: " + (new BLAKE2b).run(arrayBuffer, ["128", "Hex", {string: "", option: "UTF8"}]) +
|
||||
"\nBLAKE2b-160: " + (new BLAKE2b).run(arrayBuffer, ["160", "Hex", {string: "", option: "UTF8"}]) +
|
||||
"\nBLAKE2b-256: " + (new BLAKE2b).run(arrayBuffer, ["256", "Hex", {string: "", option: "UTF8"}]) +
|
||||
"\nBLAKE2b-384: " + (new BLAKE2b).run(arrayBuffer, ["384", "Hex", {string: "", option: "UTF8"}]) +
|
||||
"\nBLAKE2b-512: " + (new BLAKE2b).run(arrayBuffer, ["512", "Hex", {string: "", option: "UTF8"}]) +
|
||||
"\nBLAKE2s-128: " + (new BLAKE2s).run(arrayBuffer, ["128", "Hex", {string: "", option: "UTF8"}]) +
|
||||
"\nBLAKE2s-160: " + (new BLAKE2s).run(arrayBuffer, ["160", "Hex", {string: "", option: "UTF8"}]) +
|
||||
"\nBLAKE2s-256: " + (new BLAKE2s).run(arrayBuffer, ["256", "Hex", {string: "", option: "UTF8"}]) +
|
||||
"\nStreebog-256: " + (new Streebog).run(arrayBuffer, ["256"]) +
|
||||
"\nStreebog-512: " + (new Streebog).run(arrayBuffer, ["512"]) +
|
||||
"\nGOST: " + (new GOSTHash).run(arrayBuffer, ["D-A"]) +
|
||||
"\nSSDEEP: " + (new SSDEEP()).run(str) +
|
||||
"\nCTPH: " + (new CTPH()).run(str) +
|
||||
"\n\nChecksums:" +
|
||||
"\nFletcher-8: " + (new Fletcher8Checksum).run(byteArray, []) +
|
||||
"\nFletcher-16: " + (new Fletcher16Checksum).run(byteArray, []) +
|
||||
"\nFletcher-32: " + (new Fletcher32Checksum).run(byteArray, []) +
|
||||
"\nFletcher-64: " + (new Fletcher64Checksum).run(byteArray, []) +
|
||||
"\nAdler-32: " + (new Adler32Checksum).run(byteArray, []) +
|
||||
"\nCRC-8: " + (new CRC8Checksum).run(arrayBuffer, ["CRC-8"]) +
|
||||
"\nCRC-16: " + (new CRC16Checksum).run(arrayBuffer, []) +
|
||||
"\nCRC-32: " + (new CRC32Checksum).run(arrayBuffer, []);
|
||||
const [length, includeNames] = args;
|
||||
this.inputArrayBuffer = input;
|
||||
this.inputStr = Utils.arrayBufferToStr(input, false);
|
||||
this.inputByteArray = new Uint8Array(input);
|
||||
|
||||
let digest, output = "";
|
||||
// iterate over each of the hashes
|
||||
this.hashes.forEach(hash => {
|
||||
digest = this.executeAlgo(hash.algo, hash.inputType, hash.params || []);
|
||||
output += this.formatDigest(digest, length, includeNames, hash.name);
|
||||
});
|
||||
|
||||
if (length === "All") {
|
||||
output += "\nChecksums:\n";
|
||||
this.checksums.forEach(checksum => {
|
||||
digest = this.executeAlgo(checksum.algo, checksum.inputType, checksum.params || []);
|
||||
output += this.formatDigest(digest, length, includeNames, checksum.name);
|
||||
});
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a hash or checksum algorithm
|
||||
*
|
||||
* @param {Function} algo - The hash or checksum algorithm
|
||||
* @param {string} inputType
|
||||
* @param {Object[]} [params=[]]
|
||||
* @returns {string}
|
||||
*/
|
||||
executeAlgo(algo, inputType, params=[]) {
|
||||
let digest = null;
|
||||
switch (inputType) {
|
||||
case "arrayBuffer":
|
||||
digest = algo.run(this.inputArrayBuffer, params);
|
||||
break;
|
||||
case "str":
|
||||
digest = algo.run(this.inputStr, params);
|
||||
break;
|
||||
case "byteArray":
|
||||
digest = algo.run(this.inputByteArray, params);
|
||||
break;
|
||||
default:
|
||||
throw new OperationError("Unknown hash input type: " + inputType);
|
||||
}
|
||||
|
||||
return digest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the digest depending on user-specified arguments
|
||||
* @param {string} digest
|
||||
* @param {string} length
|
||||
* @param {boolean} includeNames
|
||||
* @param {string} name
|
||||
* @returns {string}
|
||||
*/
|
||||
formatDigest(digest, length, includeNames, name) {
|
||||
if (length !== "All" && (digest.length * 4) !== parseInt(length, 10))
|
||||
return "";
|
||||
|
||||
if (!includeNames)
|
||||
return digest + "\n";
|
||||
|
||||
return `${name}:${" ".repeat(13-name.length)}${digest}\n`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default GenerateAllHashes;
|
||||
|
|
85
src/core/operations/GenerateDeBruijnSequence.mjs
Normal file
85
src/core/operations/GenerateDeBruijnSequence.mjs
Normal file
|
@ -0,0 +1,85 @@
|
|||
/**
|
||||
* @author gchq77703 [gchq77703@gchq.gov.uk]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* Generate De Bruijn Sequence operation
|
||||
*/
|
||||
class GenerateDeBruijnSequence extends Operation {
|
||||
|
||||
/**
|
||||
* GenerateDeBruijnSequence constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Generate De Bruijn Sequence";
|
||||
this.module = "Default";
|
||||
this.description = "Generates rolling keycode combinations given a certain alphabet size and key length.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/De_Bruijn_sequence";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Alphabet size (k)",
|
||||
type: "number",
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
name: "Key length (n)",
|
||||
type: "number",
|
||||
value: 3
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [k, n] = args;
|
||||
|
||||
if (k < 2 || k > 9) {
|
||||
throw new OperationError("Invalid alphabet size, required to be between 2 and 9 (inclusive).");
|
||||
}
|
||||
|
||||
if (n < 2) {
|
||||
throw new OperationError("Invalid key length, required to be at least 2.");
|
||||
}
|
||||
|
||||
if (Math.pow(k, n) > 50000) {
|
||||
throw new OperationError("Too many permutations, please reduce k^n to under 50,000.");
|
||||
}
|
||||
|
||||
const a = new Array(k * n).fill(0);
|
||||
const sequence = [];
|
||||
|
||||
(function db(t = 1, p = 1) {
|
||||
if (t > n) {
|
||||
if (n % p !== 0) return;
|
||||
for (let j = 1; j <= p; j++) {
|
||||
sequence.push(a[j]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
a[t] = a[t - p];
|
||||
db(t + 1, p);
|
||||
for (let j = a[t - p] + 1; j < k; j++) {
|
||||
a[t] = j;
|
||||
db(t + 1, t);
|
||||
}
|
||||
})();
|
||||
|
||||
return sequence.join("");
|
||||
}
|
||||
}
|
||||
|
||||
export default GenerateDeBruijnSequence;
|
102
src/core/operations/GenerateECDSAKeyPair.mjs
Normal file
102
src/core/operations/GenerateECDSAKeyPair.mjs
Normal file
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
* @author cplussharp
|
||||
* @copyright Crown Copyright 2021
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { cryptNotice } from "../lib/Crypt.mjs";
|
||||
import r from "jsrsasign";
|
||||
|
||||
/**
|
||||
* Generate ECDSA Key Pair operation
|
||||
*/
|
||||
class GenerateECDSAKeyPair extends Operation {
|
||||
|
||||
/**
|
||||
* GenerateECDSAKeyPair constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Generate ECDSA Key Pair";
|
||||
this.module = "Ciphers";
|
||||
this.description = `Generate an ECDSA key pair with a given Curve.<br><br>${cryptNotice}`;
|
||||
this.infoURL = "https://wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Elliptic Curve",
|
||||
type: "option",
|
||||
value: [
|
||||
"P-256",
|
||||
"P-384",
|
||||
"P-521"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Output Format",
|
||||
type: "option",
|
||||
value: [
|
||||
"PEM",
|
||||
"DER",
|
||||
"JWK"
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
async run(input, args) {
|
||||
const [curveName, outputFormat] = args;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let internalCurveName;
|
||||
switch (curveName) {
|
||||
case "P-256":
|
||||
internalCurveName = "secp256r1";
|
||||
break;
|
||||
case "P-384":
|
||||
internalCurveName = "secp384r1";
|
||||
break;
|
||||
case "P-521":
|
||||
internalCurveName = "secp521r1";
|
||||
break;
|
||||
}
|
||||
const keyPair = r.KEYUTIL.generateKeypair("EC", internalCurveName);
|
||||
|
||||
let pubKey;
|
||||
let privKey;
|
||||
let result;
|
||||
switch (outputFormat) {
|
||||
case "PEM":
|
||||
pubKey = r.KEYUTIL.getPEM(keyPair.pubKeyObj).replace(/\r/g, "");
|
||||
privKey = r.KEYUTIL.getPEM(keyPair.prvKeyObj, "PKCS8PRV").replace(/\r/g, "");
|
||||
result = pubKey + "\n" + privKey;
|
||||
break;
|
||||
case "DER":
|
||||
result = keyPair.prvKeyObj.prvKeyHex;
|
||||
break;
|
||||
case "JWK":
|
||||
pubKey = r.KEYUTIL.getJWKFromKey(keyPair.pubKeyObj);
|
||||
pubKey.key_ops = ["verify"]; // eslint-disable-line camelcase
|
||||
pubKey.kid = "PublicKey";
|
||||
privKey = r.KEYUTIL.getJWKFromKey(keyPair.prvKeyObj);
|
||||
privKey.key_ops = ["sign"]; // eslint-disable-line camelcase
|
||||
privKey.kid = "PrivateKey";
|
||||
result = JSON.stringify({keys: [privKey, pubKey]}, null, 4);
|
||||
break;
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default GenerateECDSAKeyPair;
|
|
@ -10,8 +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 jimplib from "jimp/es/index.js";
|
||||
const jimp = jimplib.default ? jimplib.default : jimplib;
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Generate Image operation
|
||||
|
@ -82,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...");
|
||||
|
@ -96,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);
|
||||
}
|
||||
}
|
||||
|
@ -140,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})`);
|
||||
|
@ -152,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})`);
|
||||
|
|
|
@ -44,7 +44,7 @@ class GenerateQRCode extends Operation {
|
|||
{
|
||||
"name": "Margin (num modules)",
|
||||
"type": "number",
|
||||
"value": 2,
|
||||
"value": 4,
|
||||
"min": 0
|
||||
},
|
||||
{
|
||||
|
|
53
src/core/operations/GetAllCasings.mjs
Normal file
53
src/core/operations/GetAllCasings.mjs
Normal file
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* @author n1073645 [n1073645@gmail.com]
|
||||
* @copyright Crown Copyright 2020
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
|
||||
/**
|
||||
* Permutate String operation
|
||||
*/
|
||||
class GetAllCasings extends Operation {
|
||||
|
||||
/**
|
||||
* GetAllCasings constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Get All Casings";
|
||||
this.module = "Default";
|
||||
this.description = "Outputs all possible casing variations of a string.";
|
||||
this.infoURL = "";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const length = input.length;
|
||||
const max = 1 << length;
|
||||
input = input.toLowerCase();
|
||||
let result = "";
|
||||
|
||||
for (let i = 0; i < max; i++) {
|
||||
const temp = input.split("");
|
||||
for (let j = 0; j < length; j++) {
|
||||
if (((i >> j) & 1) === 1) {
|
||||
temp[j] = temp[j].toUpperCase();
|
||||
}
|
||||
}
|
||||
result += temp.join("") + "\n";
|
||||
}
|
||||
return result.slice(0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
export default GetAllCasings;
|
|
@ -68,8 +68,8 @@ class HammingDistance extends Operation {
|
|||
samples[0] = fromHex(samples[0]);
|
||||
samples[1] = fromHex(samples[1]);
|
||||
} else {
|
||||
samples[0] = Utils.strToByteArray(samples[0]);
|
||||
samples[1] = Utils.strToByteArray(samples[1]);
|
||||
samples[0] = new Uint8Array(Utils.strToArrayBuffer(samples[0]));
|
||||
samples[1] = new Uint8Array(Utils.strToArrayBuffer(samples[1]));
|
||||
}
|
||||
|
||||
let dist = 0;
|
||||
|
|
|
@ -9,8 +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 jimplib from "jimp/es/index.js";
|
||||
const jimp = jimplib.default ? jimplib.default : jimplib;
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Image Brightness / Contrast operation
|
||||
|
@ -61,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})`);
|
||||
}
|
||||
|
@ -79,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,8 +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 jimplib from "jimp/es/index.js";
|
||||
const jimp = jimplib.default ? jimplib.default : jimplib;
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Image Filter operation
|
||||
|
@ -55,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})`);
|
||||
}
|
||||
|
@ -70,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,8 +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 jimplib from "jimp/es/index.js";
|
||||
const jimp = jimplib.default ? jimplib.default : jimplib;
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Image Hue/Saturation/Lightness operation
|
||||
|
@ -69,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})`);
|
||||
}
|
||||
|
@ -107,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,8 +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 jimplib from "jimp/es/index.js";
|
||||
const jimp = jimplib.default ? jimplib.default : jimplib;
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Image Opacity operation
|
||||
|
@ -54,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})`);
|
||||
}
|
||||
|
@ -65,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) {
|
||||
|
|
|
@ -78,7 +78,7 @@ The graph shows the IC of the input data. A low IC generally means that the text
|
|||
|
||||
<script type='application/javascript'>
|
||||
var canvas = document.getElementById("chart-area"),
|
||||
parentRect = canvas.parentNode.getBoundingClientRect(),
|
||||
parentRect = canvas.closest(".cm-scroller").getBoundingClientRect(),
|
||||
ic = ${ic};
|
||||
|
||||
canvas.width = parentRect.width * 0.95;
|
||||
|
|
|
@ -9,8 +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 jimplib from "jimp/es/index.js";
|
||||
const jimp = jimplib.default ? jimplib.default : jimplib;
|
||||
import Jimp from "jimp/es/index.js";
|
||||
|
||||
/**
|
||||
* Invert Image operation
|
||||
|
@ -45,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})`);
|
||||
}
|
||||
|
@ -56,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) {
|
||||
|
|
73
src/core/operations/JA4Fingerprint.mjs
Normal file
73
src/core/operations/JA4Fingerprint.mjs
Normal file
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import {toJA4} from "../lib/JA4.mjs";
|
||||
|
||||
/**
|
||||
* JA4 Fingerprint operation
|
||||
*/
|
||||
class JA4Fingerprint extends Operation {
|
||||
|
||||
/**
|
||||
* JA4Fingerprint constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "JA4 Fingerprint";
|
||||
this.module = "Crypto";
|
||||
this.description = "Generates a JA4 fingerprint to help identify TLS clients based on hashing together values from the Client Hello.<br><br>Input: A hex stream of the TLS or QUIC Client Hello packet application layer.";
|
||||
this.infoURL = "https://medium.com/foxio/ja4-network-fingerprinting-9376fe9ca637";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Input format",
|
||||
type: "option",
|
||||
value: ["Hex", "Base64", "Raw"]
|
||||
},
|
||||
{
|
||||
name: "Output format",
|
||||
type: "option",
|
||||
value: ["JA4", "JA4 Original Rendering", "JA4 Raw", "JA4 Raw Original Rendering", "All"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [inputFormat, outputFormat] = args;
|
||||
input = Utils.convertToByteArray(input, inputFormat);
|
||||
const ja4 = toJA4(new Uint8Array(input));
|
||||
|
||||
// Output
|
||||
switch (outputFormat) {
|
||||
case "JA4":
|
||||
return ja4.JA4;
|
||||
case "JA4 Original Rendering":
|
||||
return ja4.JA4_o;
|
||||
case "JA4 Raw":
|
||||
return ja4.JA4_r;
|
||||
case "JA4 Raw Original Rendering":
|
||||
return ja4.JA4_ro;
|
||||
case "All":
|
||||
default:
|
||||
return `JA4: ${ja4.JA4}
|
||||
JA4_o: ${ja4.JA4_o}
|
||||
JA4_r: ${ja4.JA4_r}
|
||||
JA4_ro: ${ja4.JA4_ro}`;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default JA4Fingerprint;
|
66
src/core/operations/JA4ServerFingerprint.mjs
Normal file
66
src/core/operations/JA4ServerFingerprint.mjs
Normal file
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* @author n1474335 [n1474335@gmail.com]
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import {toJA4S} from "../lib/JA4.mjs";
|
||||
|
||||
/**
|
||||
* JA4Server Fingerprint operation
|
||||
*/
|
||||
class JA4ServerFingerprint extends Operation {
|
||||
|
||||
/**
|
||||
* JA4ServerFingerprint constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "JA4Server Fingerprint";
|
||||
this.module = "Crypto";
|
||||
this.description = "Generates a JA4Server Fingerprint (JA4S) to help identify TLS servers or sessions based on hashing together values from the Server Hello.<br><br>Input: A hex stream of the TLS or QUIC Server Hello packet application layer.";
|
||||
this.infoURL = "https://medium.com/foxio/ja4-network-fingerprinting-9376fe9ca637";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Input format",
|
||||
type: "option",
|
||||
value: ["Hex", "Base64", "Raw"]
|
||||
},
|
||||
{
|
||||
name: "Output format",
|
||||
type: "option",
|
||||
value: ["JA4S", "JA4S Raw", "Both"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [inputFormat, outputFormat] = args;
|
||||
input = Utils.convertToByteArray(input, inputFormat);
|
||||
const ja4s = toJA4S(new Uint8Array(input));
|
||||
|
||||
// Output
|
||||
switch (outputFormat) {
|
||||
case "JA4S":
|
||||
return ja4s.JA4S;
|
||||
case "JA4S Raw":
|
||||
return ja4s.JA4S_r;
|
||||
case "Both":
|
||||
default:
|
||||
return `JA4S: ${ja4s.JA4S}\nJA4S_r: ${ja4s.JA4S_r}`;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default JA4ServerFingerprint;
|
|
@ -4,7 +4,7 @@
|
|||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import jpath from "jsonpath";
|
||||
import {JSONPath} from "jsonpath-plus";
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
|
@ -27,14 +27,14 @@ class JPathExpression extends Operation {
|
|||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Query",
|
||||
"type": "string",
|
||||
"value": ""
|
||||
name: "Query",
|
||||
type: "string",
|
||||
value: ""
|
||||
},
|
||||
{
|
||||
"name": "Result delimiter",
|
||||
"type": "binaryShortString",
|
||||
"value": "\\n"
|
||||
name: "Result delimiter",
|
||||
type: "binaryShortString",
|
||||
value: "\\n"
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -46,17 +46,19 @@ class JPathExpression extends Operation {
|
|||
*/
|
||||
run(input, args) {
|
||||
const [query, delimiter] = args;
|
||||
let results,
|
||||
obj;
|
||||
let results, jsonObj;
|
||||
|
||||
try {
|
||||
obj = JSON.parse(input);
|
||||
jsonObj = JSON.parse(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Invalid input JSON: ${err.message}`);
|
||||
}
|
||||
|
||||
try {
|
||||
results = jpath.query(obj, query);
|
||||
results = JSONPath({
|
||||
path: query,
|
||||
json: jsonObj
|
||||
});
|
||||
} catch (err) {
|
||||
throw new OperationError(`Invalid JPath expression: ${err.message}`);
|
||||
}
|
||||
|
|
|
@ -5,8 +5,10 @@
|
|||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import vkbeautify from "vkbeautify";
|
||||
import JSON5 from "json5";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* JSON Beautify operation
|
||||
|
@ -21,19 +23,25 @@ class JSONBeautify extends Operation {
|
|||
|
||||
this.name = "JSON Beautify";
|
||||
this.module = "Code";
|
||||
this.description = "Indents and prettifies JavaScript Object Notation (JSON) code.";
|
||||
this.description = "Indents and pretty prints JavaScript Object Notation (JSON) code.<br><br>Tags: json viewer, prettify, syntax highlighting";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.presentType = "html";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Indent string",
|
||||
"type": "binaryShortString",
|
||||
"value": " "
|
||||
name: "Indent string",
|
||||
type: "binaryShortString",
|
||||
value: " "
|
||||
},
|
||||
{
|
||||
"name": "Sort Object Keys",
|
||||
"type": "boolean",
|
||||
"value": false
|
||||
name: "Sort Object Keys",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: "Formatted",
|
||||
type: "boolean",
|
||||
value: true
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -44,35 +52,193 @@ class JSONBeautify extends Operation {
|
|||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [indentStr, sortBool] = args;
|
||||
|
||||
if (!input) return "";
|
||||
if (sortBool) {
|
||||
input = JSON.stringify(JSONBeautify._sort(JSON.parse(input)));
|
||||
|
||||
const [indentStr, sortBool] = args;
|
||||
let json = null;
|
||||
|
||||
try {
|
||||
json = JSON5.parse(input);
|
||||
} catch (err) {
|
||||
throw new OperationError("Unable to parse input as JSON.\n" + err);
|
||||
}
|
||||
return vkbeautify.json(input, indentStr);
|
||||
|
||||
if (sortBool) json = sortKeys(json);
|
||||
|
||||
return JSON.stringify(json, null, indentStr);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sort JSON representation of an object
|
||||
* Adds various dynamic features to the JSON blob
|
||||
*
|
||||
* @author Phillip Nordwall [phillip.nordwall@gmail.com]
|
||||
* @private
|
||||
* @param {object} o
|
||||
* @returns {object}
|
||||
* @param {string} data
|
||||
* @param {Object[]} args
|
||||
* @returns {html}
|
||||
*/
|
||||
static _sort(o) {
|
||||
if (Array.isArray(o)) {
|
||||
return o.map(JSONBeautify._sort);
|
||||
} else if ("[object Object]" === Object.prototype.toString.call(o)) {
|
||||
return Object.keys(o).sort().reduce(function(a, k) {
|
||||
a[k] = JSONBeautify._sort(o[k]);
|
||||
return a;
|
||||
}, {});
|
||||
present(data, args) {
|
||||
const formatted = args[2];
|
||||
if (!formatted) return Utils.escapeHtml(data);
|
||||
|
||||
const json = JSON5.parse(data);
|
||||
const options = {
|
||||
withLinks: true,
|
||||
bigNumbers: true
|
||||
};
|
||||
let html = '<div class="json-document">';
|
||||
|
||||
if (isCollapsable(json)) {
|
||||
const isArr = json instanceof Array;
|
||||
html += '<details open class="json-details">' +
|
||||
`<summary class="json-summary ${isArr ? "json-arr" : "json-obj"}"></summary>` +
|
||||
json2html(json, options) +
|
||||
"</details>";
|
||||
} else {
|
||||
html += json2html(json, options);
|
||||
}
|
||||
return o;
|
||||
|
||||
html += "</div>";
|
||||
return html;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort keys in a JSON object
|
||||
*
|
||||
* @author Phillip Nordwall [phillip.nordwall@gmail.com]
|
||||
* @param {object} o
|
||||
* @returns {object}
|
||||
*/
|
||||
function sortKeys(o) {
|
||||
if (Array.isArray(o)) {
|
||||
return o.map(sortKeys);
|
||||
} else if ("[object Object]" === Object.prototype.toString.call(o)) {
|
||||
return Object.keys(o).sort().reduce(function(a, k) {
|
||||
a[k] = sortKeys(o[k]);
|
||||
return a;
|
||||
}, {});
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if arg is either an array with at least 1 element, or a dict with at least 1 key
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isCollapsable(arg) {
|
||||
return arg instanceof Object && Object.keys(arg).length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string looks like a URL, based on protocol
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isUrl(string) {
|
||||
const protocols = ["http", "https", "ftp", "ftps"];
|
||||
for (let i = 0; i < protocols.length; i++) {
|
||||
if (string.startsWith(protocols[i] + "://")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a json object into html representation
|
||||
*
|
||||
* Adapted for CyberChef by @n1474335 from jQuery json-viewer
|
||||
* @author Alexandre Bodelot <alexandre.bodelot@gmail.com>
|
||||
* @link https://github.com/abodelot/jquery.json-viewer
|
||||
* @license MIT
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
function json2html(json, options) {
|
||||
let html = "";
|
||||
if (typeof json === "string") {
|
||||
// Escape tags and quotes
|
||||
json = Utils.escapeHtml(json);
|
||||
|
||||
if (options.withLinks && isUrl(json)) {
|
||||
html += `<a href="${json}" class="json-string" target="_blank">${json}</a>`;
|
||||
} else {
|
||||
// Escape double quotes in the rendered non-URL string.
|
||||
json = json.replace(/"/g, "\\"");
|
||||
html += `<span class="json-string">"${json}"</span>`;
|
||||
}
|
||||
} else if (typeof json === "number" || typeof json === "bigint") {
|
||||
html += `<span class="json-literal">${json}</span>`;
|
||||
} else if (typeof json === "boolean") {
|
||||
html += `<span class="json-literal">${json}</span>`;
|
||||
} else if (json === null) {
|
||||
html += '<span class="json-literal">null</span>';
|
||||
} else if (json instanceof Array) {
|
||||
if (json.length > 0) {
|
||||
html += '<span class="json-bracket">[</span><ol class="json-array">';
|
||||
for (let i = 0; i < json.length; i++) {
|
||||
html += "<li>";
|
||||
|
||||
// Add toggle button if item is collapsable
|
||||
if (isCollapsable(json[i])) {
|
||||
const isArr = json[i] instanceof Array;
|
||||
html += '<details open class="json-details">' +
|
||||
`<summary class="json-summary ${isArr ? "json-arr" : "json-obj"}"></summary>` +
|
||||
json2html(json[i], options) +
|
||||
"</details>";
|
||||
} else {
|
||||
html += json2html(json[i], options);
|
||||
}
|
||||
|
||||
// Add comma if item is not last
|
||||
if (i < json.length - 1) {
|
||||
html += '<span class="json-comma">,</span>';
|
||||
}
|
||||
html += "</li>";
|
||||
}
|
||||
html += '</ol><span class="json-bracket">]</span>';
|
||||
} else {
|
||||
html += '<span class="json-bracket">[]</span>';
|
||||
}
|
||||
} else if (typeof json === "object") {
|
||||
// Optional support different libraries for big numbers
|
||||
// json.isLosslessNumber: package lossless-json
|
||||
// json.toExponential(): packages bignumber.js, big.js, decimal.js, decimal.js-light, others?
|
||||
if (options.bigNumbers && (typeof json.toExponential === "function" || json.isLosslessNumber)) {
|
||||
html += `<span class="json-literal">${json.toString()}</span>`;
|
||||
} else {
|
||||
let keyCount = Object.keys(json).length;
|
||||
if (keyCount > 0) {
|
||||
html += '<span class="json-brace">{</span><ul class="json-dict">';
|
||||
for (const key in json) {
|
||||
if (Object.prototype.hasOwnProperty.call(json, key)) {
|
||||
const safeKey = Utils.escapeHtml(key);
|
||||
html += "<li>";
|
||||
|
||||
// Add toggle button if item is collapsable
|
||||
if (isCollapsable(json[key])) {
|
||||
const isArr = json[key] instanceof Array;
|
||||
html += '<details open class="json-details">' +
|
||||
`<summary class="json-summary ${isArr ? "json-arr" : "json-obj"}">${safeKey}<span class="json-colon">:</span> </summary>` +
|
||||
json2html(json[key], options) +
|
||||
"</details>";
|
||||
} else {
|
||||
html += safeKey + '<span class="json-colon">:</span> ' + json2html(json[key], options);
|
||||
}
|
||||
|
||||
// Add comma if item is not last
|
||||
if (--keyCount > 0) {
|
||||
html += '<span class="json-comma">,</span>';
|
||||
}
|
||||
html += "</li>";
|
||||
}
|
||||
}
|
||||
html += '</ul><span class="json-brace">}</span>';
|
||||
} else {
|
||||
html += '<span class="json-brace">{}</span>';
|
||||
}
|
||||
}
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
export default JSONBeautify;
|
||||
|
|
|
@ -114,8 +114,11 @@ class JSONToCSV extends Operation {
|
|||
* @returns {string}
|
||||
*/
|
||||
escapeCellContents(data, force=false) {
|
||||
if (typeof data === "number") data = data.toString();
|
||||
if (force && typeof data !== "string") data = JSON.stringify(data);
|
||||
if (data !== "string") {
|
||||
const isPrimitive = data == null || typeof data !== "object";
|
||||
if (isPrimitive) data = `${data}`;
|
||||
else if (force) data = JSON.stringify(data);
|
||||
}
|
||||
|
||||
// Double quotes should be doubled up
|
||||
data = data.replace(/"/g, '""');
|
||||
|
|
80
src/core/operations/JWKToPem.mjs
Normal file
80
src/core/operations/JWKToPem.mjs
Normal file
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* @author cplussharp
|
||||
* @copyright Crown Copyright 2021
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import r from "jsrsasign";
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* PEM to JWK operation
|
||||
*/
|
||||
class PEMToJWK extends Operation {
|
||||
|
||||
/**
|
||||
* PEMToJWK constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "JWK to PEM";
|
||||
this.module = "PublicKey";
|
||||
this.description = "Converts Keys in JSON Web Key format to PEM format (PKCS#8).";
|
||||
this.infoURL = "https://datatracker.ietf.org/doc/html/rfc7517";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
this.checks = [
|
||||
{
|
||||
"pattern": "\"kty\":\\s*\"(EC|RSA)\"",
|
||||
"flags": "gm",
|
||||
"args": []
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const inputJson = JSON.parse(input);
|
||||
|
||||
let keys = [];
|
||||
if (Array.isArray(inputJson)) {
|
||||
// list of keys => transform all keys
|
||||
keys = inputJson;
|
||||
} else if (Array.isArray(inputJson.keys)) {
|
||||
// JSON Web Key Set => transform all keys
|
||||
keys = inputJson.keys;
|
||||
} else if (typeof inputJson === "object") {
|
||||
// single key
|
||||
keys.push(inputJson);
|
||||
} else {
|
||||
throw new OperationError("Input is not a JSON Web Key");
|
||||
}
|
||||
|
||||
let output = "";
|
||||
for (let i=0; i<keys.length; i++) {
|
||||
const jwk = keys[i];
|
||||
if (typeof jwk.kty !== "string") {
|
||||
throw new OperationError("Invalid JWK format");
|
||||
} else if ("|RSA|EC|".indexOf(jwk.kty) === -1) {
|
||||
throw new OperationError(`Unsupported JWK key type '${inputJson.kty}'`);
|
||||
}
|
||||
|
||||
const key = r.KEYUTIL.getKey(jwk);
|
||||
const pem = key.isPrivate ? r.KEYUTIL.getPEM(key, "PKCS8PRV") : r.KEYUTIL.getPEM(key);
|
||||
|
||||
// PEM ends with '\n', so a new key always starts on a new line
|
||||
output += pem;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
export default PEMToJWK;
|
|
@ -26,6 +26,13 @@ class JWTDecode extends Operation {
|
|||
this.inputType = "string";
|
||||
this.outputType = "JSON";
|
||||
this.args = [];
|
||||
this.checks = [
|
||||
{
|
||||
pattern: "^ey([A-Za-z0-9_-]+)\\.ey([A-Za-z0-9_-]+)\\.([A-Za-z0-9_-]+)$",
|
||||
flags: "",
|
||||
args: []
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -52,6 +52,7 @@ class Jump extends Operation {
|
|||
const jmpIndex = getLabelIndex(label, state);
|
||||
|
||||
if (state.numJumps >= maxJumps || jmpIndex === -1) {
|
||||
state.numJumps = 0;
|
||||
return state;
|
||||
}
|
||||
|
||||
|
|
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