Compare commits

..

39 Commits

Author SHA1 Message Date
n1474335
bbfb732d8f 7.2.3 2018-01-04 17:48:08 +00:00
n1474335
566adbcda5 'Unescape string' operation now works with capitalised hex 2018-01-04 17:48:01 +00:00
n1474335
1bff490fa4 7.2.2 2018-01-04 14:43:54 +00:00
n1474335
d38375a08c Improved error handling for file uploads 2018-01-04 14:43:49 +00:00
n1474335
0ba28dc891 Updated README.md 2018-01-02 16:02:46 +00:00
n1474335
ac9af6d2ba 7.2.1 2018-01-02 15:33:08 +00:00
n1474335
90d9e087f7 'Take bytes' and 'Drop bytes' operations now support ArrayBuffers 2018-01-02 15:33:02 +00:00
n1474335
50b24d9a56 Fixed no-trailing-space lint 2018-01-02 14:46:35 +00:00
n1474335
10f42e9a7f 7.2.0 2018-01-02 00:13:34 +00:00
n1474335
987bd303a0 Merge branch 'forge' 2018-01-02 00:13:20 +00:00
n1474335
a3f58fb831 Added 'Pseudo-Random Number Generator' operation. 2018-01-01 20:50:01 +00:00
n1474335
f52f5a0edb Added 'RC2 Encrypt' and 'RC2 Decrypt' operations. 2018-01-01 19:50:06 +00:00
n1474335
b58942f69a Rewrote PBKDF2 operation to use Forge instead of CryptoJS and improved the API for both PBKDF2 and EVP operations. 2018-01-01 18:49:22 +00:00
n1474335
68e52d1645 Removed CryptoJS encodings from Blowfish operations. 2018-01-01 17:54:45 +00:00
n1474335
9fc7e6cd98 Rewrote AES, DES and Triple DES operations to use Forge instead of CryptoJS, simplifying their options and adding many tests. Removed Rabbit operations. Fixes #63 and #210. 2018-01-01 16:09:58 +00:00
n1474335
87f346d88c 7.1.0 2017-12-29 17:48:22 +00:00
n1474335
e423ff2639 Merge branch 'feature-logging' 2017-12-29 17:48:14 +00:00
n1474335
fa6905ef00 Added more comprehensive logging to FlowControl ops and added '>' prefix to all ChefWorker logs to improve clarity 2017-12-29 17:32:23 +00:00
n1474335
8684bc0158 Removed duplicate logging message 2017-12-28 18:33:59 +00:00
n1474335
a96eb450de Improved Recipe logging 2017-12-28 18:24:29 +00:00
n1474335
d079420d46 Added logging with configurable levels to make debugging easier. 2017-12-28 18:17:38 +00:00
n1474335
124ff83206 7.0.0 2017-12-28 16:10:39 +00:00
n1474335
2c9e67ad1d Merge branch 'feature-files' 2017-12-28 16:09:11 +00:00
n1474335
75a554e215 'To Base64' and 'To Hexdump' operations now support ArrayBuffers 2017-12-28 15:59:58 +00:00
n1474335
849d41ee56 Removed padLeft and padRight in favour of String.prototype.padStart and padEnd. 'To Hex' now supports ArrayBuffers. 2017-12-28 14:38:57 +00:00
n1474335
e18ec5f2b2 Changed inputType to ArrayBuffer for 'Frequency distribution', 'Chi Square' and 'Extract EXIF' operations. 2017-12-28 00:24:16 +00:00
n1474335
50e4daeaf2 Output info tidied up for file outputs 2017-12-27 23:05:32 +00:00
n1474335
f7f07f2cb5 Loading a new file only causes a statechange event once the whole file has loaded 2017-12-27 15:52:38 +00:00
n1474335
e2161ec934 Resolved conflicts 2017-12-27 12:50:29 +00:00
n1474335
1e4d2ba90d Updated documentation for file support 2017-12-27 12:45:22 +00:00
n1474335
caf794b01d Threshold for treating output as a file is now configurable 2017-12-27 12:29:10 +00:00
n1474335
e81122739b Files can now be moved from the output to the input 2017-12-27 02:26:24 +00:00
n1474335
a0aa363203 Download filenames now persist 2017-12-27 02:01:17 +00:00
n1474335
bad0816115 Output files can be viewed in slices 2017-12-27 01:52:41 +00:00
n1474335
53a3f3d452 Changed inputType for file magic byte operations to ArrayBuffer 2017-12-26 22:05:10 +00:00
n1474335
ff94172b3c Output files can now be downloaded using FileSaver (supports large files ~500MB) 2017-12-26 01:32:51 +00:00
n1474335
af71ca6a25 Output over 1MiB is passed back as an ArrayBuffer and an output file card is displayed. 2017-12-26 00:44:40 +00:00
n1474335
0e7989111f Removed CryptoJS from Utils.js. UTF8 conversion is now achieved with the much smaller and actively maintained utf8 library. 2017-12-25 23:11:52 +00:00
n1474335
4e00ac9300 Files are now uploaded in a worker and not displayed in the input by default. Added ArrayBuffer Dish type. 2017-12-18 20:39:55 +00:00
49 changed files with 3590 additions and 1644 deletions

View File

@@ -35,7 +35,6 @@
}],
// disable rules from base configurations
"no-console": "off",
"no-control-regex": "off",
// stylistic conventions
@@ -90,6 +89,7 @@
"$": false,
"jQuery": false,
"moment": false,
"log": false,
"COMPILE_TIME": false,
"COMPILE_MSG": false,

View File

@@ -27,7 +27,7 @@ Cryptographic operations in CyberChef should not be relied upon to provide secur
There are four main areas in CyberChef:
1. The **input** box in the top right, where you can paste, type or drag the data you want to operate on.
1. The **input** box in the top right, where you can paste, type or drag the text or file you want to operate on.
2. The **output** box in the bottom right, where the outcome of your processing will be displayed.
3. The **operations** list on the far left, where you can find all the operations that CyberChef is capable of in categorised lists, or by searching.
4. The **recipe** area in the middle, where you can drag the operations that you want to use and specify arguments and options.
@@ -42,32 +42,32 @@ You can use as many operations as you like in simple or complex ways. Some examp
- [Display multiple timestamps as full dates][7]
- [Carry out different operations on data of different types][8]
- [Use parts of the input as arguments to operations][9]
- [Perform AES decryption, extracting the IV from the beginning of the cipher stream][10]
## Features
- Drag and drop
- Operations can be dragged in and out of the recipe list, or reorganised.
- Files can be dragged over the input box to load them directly.
- Files can be dragged over the input box to load them directly into the browser.
- Auto Bake
- Whenever you modify the input or the recipe, CyberChef will automatically bake for you and produce the output immediately.
- Whenever you modify the input or the recipe, CyberChef will automatically "bake" for you and produce the output immediately.
- This can be turned off and operated manually if it is affecting performance (if the input is very large, for instance).
- If any bake takes longer than 200 milliseconds, auto bake will be switched off automatically to prevent further performance issues.
- Breakpoints
- You can set breakpoints on any operation in your recipe to pause execution before running it.
- You can also step through the recipe one operation at a time to see what the data looks like at each stage.
- Save and load recipes
- If you come up with an awesome recipe that you know youll want to use again, just click save and add it to your local storage. It'll be waiting for you next time you visit CyberChef.
- You can also copy a URL which includes your recipe and input which can be shared with others.
- If you come up with an awesome recipe that you know youll want to use again, just click "Save recipe" and add it to your local storage. It'll be waiting for you next time you visit CyberChef.
- You can also copy the URL, which includes your recipe and input, to easily share it with others.
- Search
- If you know the name of the operation you want or a word associated with it, start typing it into the search field and any matching operations will immediately be shown.
- Highlighting
- When you highlight text in the input or output, the offset and length values will be displayed and, if possible, the corresponding data will be highlighted in the output or input respectively (example: [highlight the word 'question' in the input to see where it appears in the output][10]).
- When you highlight text in the input or output, the offset and length values will be displayed and, if possible, the corresponding data will be highlighted in the output or input respectively (example: [highlight the word 'question' in the input to see where it appears in the output][11]).
- Save to file and load from file
- You can save the output to a file at any time or load a file by dragging and dropping it into the input field (note that files larger than about 500kb may cause your browser to hang or even crash due to the way that browsers handle large amounts of textual data).
- You can save the output to a file at any time or load a file by dragging and dropping it into the input field. Files up to around 500MB are supported (depending on your browser), however some operations may take a very long time to run over this much data.
- CyberChef is entirely client-side
- It should be noted that none of your input or recipe configuration is ever sent to the CyberChef web server - all processing is carried out within your browser, on your own computer.
- Due to this feature, CyberChef can be compiled into a single HTML file. You can download this file and drop it into a virtual machine, share it with other people, or use it independently on your desktop.
- It should be noted that none of your recipe configuration or input (either text or files) is ever sent to the CyberChef web server - all processing is carried out within your browser, on your own computer.
- Due to this feature, CyberChef can be compiled into a single HTML file. You can download this file and drop it into a virtual machine, share it with other people, or use it independently on your local machine.
## Browser support
@@ -100,6 +100,7 @@ CyberChef is released under the [Apache 2.0 Licence](https://www.apache.org/lice
[5]: https://gchq.github.io/CyberChef/#recipe=From_Hexdump()Gunzip()&input=MDAwMDAwMDAgIDFmIDhiIDA4IDAwIDEyIGJjIGYzIDU3IDAwIGZmIDBkIGM3IGMxIDA5IDAwIDIwICB8Li4uLi6881cu/y7HwS4uIHwKMDAwMDAwMTAgIDA4IDA1IGQwIDU1IGZlIDA0IDJkIGQzIDA0IDFmIGNhIDhjIDQ0IDIxIDViIGZmICB8Li7QVf4uLdMuLsouRCFb/3wKMDAwMDAwMjAgIDYwIGM3IGQ3IDAzIDE2IGJlIDQwIDFmIDc4IDRhIDNmIDA5IDg5IDBiIDlhIDdkICB8YMfXLi6%2BQC54Sj8uLi4ufXwKMDAwMDAwMzAgIDRlIGM4IDRlIDZkIDA1IDFlIDAxIDhiIDRjIDI0IDAwIDAwIDAwICAgICAgICAgICB8TshObS4uLi5MJC4uLnw
[6]: https://gchq.github.io/CyberChef/#recipe=RC4(%7B'option':'UTF8','string':'secret'%7D,'Hex','Hex')Disassemble_x86('64','Full%20x86%20architecture',16,0,true,true)&input=MjFkZGQyNTQwMTYwZWU2NWZlMDc3NzEwM2YyYTM5ZmJlNWJjYjZhYTBhYWJkNDE0ZjkwYzZjYWY1MzEyNzU0YWY3NzRiNzZiM2JiY2QxOTNjYjNkZGZkYmM1YTI2NTMzYTY4NmI1OWI4ZmVkNGQzODBkNDc0NDIwMWFlYzIwNDA1MDcxMzhlMmZlMmIzOTUwNDQ2ZGIzMWQyYmM2MjliZTRkM2YyZWIwMDQzYzI5M2Q3YTVkMjk2MmMwMGZlNmRhMzAwNzJkOGM1YTZiNGZlN2Q4NTlhMDQwZWVhZjI5OTczMzYzMDJmNWEwZWMxOQ
[7]: https://gchq.github.io/CyberChef/#recipe=Fork('%5C%5Cn','%5C%5Cn',false)From_UNIX_Timestamp('Seconds%20(s)')&input=OTc4MzQ2ODAwCjEwMTI2NTEyMDAKMTA0NjY5NjQwMAoxMDgxMDg3MjAwCjExMTUzMDUyMDAKMTE0OTYwOTYwMA
[8]: https://gchq.github.ioeCyberChef/#recipe=Fork('%5C%5Cn','%5C%5Cn',false)Conditional_Jump('1',false,'base64',10)To_Hex('Space')Return()Label('base64')To_Base64('A-Za-z0-9%2B/%3D')&input=U29tZSBkYXRhIHdpdGggYSAxIGluIGl0ClNvbWUgZGF0YSB3aXRoIGEgMiBpbiBpdA
[8]: https://gchq.github.io/CyberChef/#recipe=Fork('%5C%5Cn','%5C%5Cn',false)Conditional_Jump('1',false,'base64',10)To_Hex('Space')Return()Label('base64')To_Base64('A-Za-z0-9%2B/%3D')&input=U29tZSBkYXRhIHdpdGggYSAxIGluIGl0ClNvbWUgZGF0YSB3aXRoIGEgMiBpbiBpdA
[9]: https://gchq.github.io/CyberChef/#recipe=Register('key%3D(%5B%5C%5Cda-f%5D*)',true,false)Find_/_Replace(%7B'option':'Regex','string':'.*data%3D(.*)'%7D,'$1',true,false,true)RC4(%7B'option':'Hex','string':'$R0'%7D,'Hex','Latin1')&input=aHR0cDovL21hbHdhcmV6LmJpei9iZWFjb24ucGhwP2tleT0wZTkzMmE1YyZkYXRhPThkYjdkNWViZTM4NjYzYTU0ZWNiYjMzNGUzZGIxMQ
[10]: https://gchq.github.io/CyberChef/#recipe=XOR(%7B'option':'Hex','string':'3a'%7D,'',false)To_Hexdump(16,false,false)&input=VGhlIGFuc3dlciB0byB0aGUgdWx0aW1hdGUgcXVlc3Rpb24gb2YgbGlmZSwgdGhlIFVuaXZlcnNlLCBhbmQgZXZlcnl0aGluZyBpcyA0Mi4
[10]: https://gchq.github.io/CyberChef/#recipe=Register('(.%7B32%7D)',true,false)Drop_bytes(0,32,false)AES_Decrypt(%7B'option':'Hex','string':'1748e7179bd56570d51fa4ba287cc3e5'%7D,%7B'option':'Hex','string':'$R0'%7D,'CTR','Hex','Raw',%7B'option':'Hex','string':''%7D)&input=NTFlMjAxZDQ2MzY5OGVmNWY3MTdmNzFmNWI0NzEyYWYyMGJlNjc0YjNiZmY1M2QzODU0NjM5NmVlNjFkYWFjNDkwOGUzMTljYTNmY2Y3MDg5YmZiNmIzOGVhOTllNzgxZDI2ZTU3N2JhOWRkNmYzMTFhMzk0MjBiODk3OGU5MzAxNGIwNDJkNDQ3MjZjYWVkZjU0MzZlYWY2NTI0MjljMGRmOTRiNTIxNjc2YzdjMmNlODEyMDk3YzI3NzI3M2M3YzcyY2Q4OWFlYzhkOWZiNGEyNzU4NmNjZjZhYTBhZWUyMjRjMzRiYTNiZmRmN2FlYjFkZGQ0Nzc2MjJiOTFlNzJjOWU3MDlhYjYwZjhkYWY3MzFlYzBjYzg1Y2UwZjc0NmZmMTU1NGE1YTNlYzI5MWNhNDBmOWU2MjlhODcyNTkyZDk4OGZkZDgzNDUzNGFiYTc5YzFhZDE2NzY3NjlhN2MwMTBiZjA0NzM5ZWNkYjY1ZDk1MzAyMzcxZDYyOWQ5ZTM3ZTdiNGEzNjFkYTQ2OGYxZWQ1MzU4OTIyZDJlYTc1MmRkMTFjMzY2ZjMwMTdiMTRhYTAxMWQyYWYwM2M0NGY5NTU3OTA5OGExNWUzY2Y5YjQ0ODZmOGZmZTljMjM5ZjM0ZGU3MTUxZjZjYTY1MDBmZTRiODUwYzNmMWMwMmU4MDFjYWYzYTI0NDY0NjE0ZTQyODAxNjE1YjhmZmFhMDdhYzgyNTE0OTNmZmRhN2RlNWRkZjMzNjg4ODBjMmI5NWIwMzBmNDFmOGYxNTA2NmFkZDA3MWE2NmNmNjBlNWY0NmYzYTIzMGQzOTdiNjUyOTYzYTIxYTUzZg
[11]: https://gchq.github.io/CyberChef/#recipe=XOR(%7B'option':'Hex','string':'3a'%7D,'',false)To_Hexdump(16,false,false)&input=VGhlIGFuc3dlciB0byB0aGUgdWx0aW1hdGUgcXVlc3Rpb24gb2YgbGlmZSwgdGhlIFVuaXZlcnNlLCBhbmQgZXZlcnl0aGluZyBpcyA0Mi4

1273
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "cyberchef",
"version": "6.8.0",
"version": "7.2.3",
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
"author": "n1474335 <n1474335@gmail.com>",
"homepage": "https://gchq.github.io/CyberChef",
@@ -78,6 +78,7 @@
"esmangle": "^1.0.1",
"esprima": "^4.0.0",
"exif-parser": "^0.1.12",
"file-saver": "^1.3.3",
"google-code-prettify": "^1.0.5",
"jquery": "^3.2.1",
"js-crc": "^0.2.0",
@@ -86,14 +87,18 @@
"jsonpath": "^1.0.0",
"jsrsasign": "8.0.4",
"lodash": "^4.17.4",
"loglevel": "^1.6.0",
"loglevel-message-prefix": "^3.0.0",
"moment": "^2.20.1",
"moment-timezone": "^0.5.14",
"node-forge": "^0.7.1",
"node-md6": "^0.1.0",
"nwmatcher": "^1.4.3",
"otp": "^0.1.3",
"sladex-blowfish": "^0.8.1",
"sortablejs": "^1.7.0",
"split.js": "^1.3.5",
"utf8": "^3.0.0",
"vkbeautify": "^0.99.3",
"xmldom": "^0.1.27",
"xpath": "0.0.27",

View File

@@ -19,7 +19,7 @@ const Chef = function() {
/**
* Runs the recipe over the input.
*
* @param {string} inputText - The input data as a string
* @param {string|ArrayBuffer} input - The input data as a string or ArrayBuffer
* @param {Object[]} recipeConfig - The recipe configuration object
* @param {Object} options - The options object storing various user choices
* @param {boolean} options.attempHighlight - Whether or not to attempt highlighting
@@ -33,7 +33,8 @@ const Chef = function() {
* @returns {number} response.duration - The number of ms it took to execute the recipe
* @returns {number} response.error - The error object thrown by a failed operation (false if no error)
*/
Chef.prototype.bake = async function(inputText, recipeConfig, options, progress, step) {
Chef.prototype.bake = async function(input, recipeConfig, options, progress, step) {
log.debug("Chef baking");
let startTime = new Date().getTime(),
recipe = new Recipe(recipeConfig),
containsFc = recipe.containsFlowControl(),
@@ -62,23 +63,30 @@ Chef.prototype.bake = async function(inputText, recipeConfig, options, progress,
// If starting from scratch, load data
if (progress === 0) {
this.dish.set(inputText, Dish.STRING);
const type = input instanceof ArrayBuffer ? Dish.ARRAY_BUFFER : Dish.STRING;
this.dish.set(input, type);
}
try {
progress = await recipe.execute(this.dish, progress);
} catch (err) {
console.log(err);
log.error(err);
error = {
displayStr: err.displayStr,
};
progress = err.progress;
}
// Depending on the size of the output, we may send it back as a string or an ArrayBuffer.
// This can prevent unnecessary casting as an ArrayBuffer can be easily downloaded as a file.
// The threshold is specified in KiB.
const threshold = (options.outputFileThreshold || 1024) * 1024;
const returnType = this.dish.size() > threshold ? Dish.ARRAY_BUFFER : Dish.STRING;
return {
result: this.dish.type === Dish.HTML ?
this.dish.get(Dish.HTML) :
this.dish.get(Dish.STRING),
this.dish.get(returnType),
type: Dish.enumLookup(this.dish.type),
progress: progress,
duration: new Date().getTime() - startTime,
@@ -105,6 +113,8 @@ Chef.prototype.bake = async function(inputText, recipeConfig, options, progress,
* @returns {number} The time it took to run the silent bake in milliseconds.
*/
Chef.prototype.silentBake = function(recipeConfig) {
log.debug("Running silent bake");
let startTime = new Date().getTime(),
recipe = new Recipe(recipeConfig),
dish = new Dish("", Dish.STRING);

View File

@@ -11,6 +11,15 @@ import Chef from "./Chef.js";
import OperationConfig from "./config/MetaConfig.js";
import OpModules from "./config/modules/Default.js";
// Add ">" to the start of all log messages in the Chef Worker
import loglevelMessagePrefix from "loglevel-message-prefix";
loglevelMessagePrefix(log, {
prefixes: [],
staticPrefixes: [">"],
prefixFormat: "%p"
});
// Set up Chef instance
self.chef = new Chef();
@@ -42,6 +51,8 @@ self.postMessage({
self.addEventListener("message", function(e) {
// Handle message
const r = e.data;
log.debug("ChefWorker receiving command '" + r.action + "'");
switch (r.action) {
case "bake":
bake(r.data);
@@ -61,6 +72,9 @@ self.addEventListener("message", function(e) {
r.data.pos
);
break;
case "setLogLevel":
log.setLevel(r.data, false);
break;
default:
break;
}
@@ -86,7 +100,7 @@ async function bake(data) {
);
self.postMessage({
action: "bakeSuccess",
action: "bakeComplete",
data: response
});
} catch (err) {
@@ -121,7 +135,7 @@ function loadRequiredModules(recipeConfig) {
let module = self.OperationConfig[op.op].module;
if (!OpModules.hasOwnProperty(module)) {
console.log("Loading module " + module);
log.info("Loading module " + module);
self.sendStatusMessage("Loading module " + module);
self.importScripts(self.docURL + "/" + module + ".js");
}

View File

@@ -8,11 +8,11 @@ import Utils from "./Utils.js";
* @license Apache-2.0
*
* @class
* @param {byteArray|string|number} value - The value of the input data.
* @param {byteArray|string|number|ArrayBuffer} value - The value of the input data.
* @param {number} type - The data type of value, see Dish enums.
*/
const Dish = function(value, type) {
this.value = value || typeof value == "string" ? value : null;
this.value = value || typeof value === "string" ? value : null;
this.type = type || Dish.BYTE_ARRAY;
};
@@ -41,6 +41,12 @@ Dish.NUMBER = 2;
* @enum
*/
Dish.HTML = 3;
/**
* Dish data type enum for ArrayBuffers.
* @readonly
* @enum
*/
Dish.ARRAY_BUFFER = 4;
/**
@@ -64,6 +70,9 @@ Dish.typeEnum = function(typeStr) {
case "html":
case "HTML":
return Dish.HTML;
case "arrayBuffer":
case "ArrayBuffer":
return Dish.ARRAY_BUFFER;
default:
throw "Invalid data type string. No matching enum.";
}
@@ -87,6 +96,8 @@ Dish.enumLookup = function(typeEnum) {
return "number";
case Dish.HTML:
return "html";
case Dish.ARRAY_BUFFER:
return "ArrayBuffer";
default:
throw "Invalid data type enum. No matching type.";
}
@@ -96,10 +107,11 @@ Dish.enumLookup = function(typeEnum) {
/**
* Sets the data value and type and then validates them.
*
* @param {byteArray|string|number} value - The value of the input data.
* @param {byteArray|string|number|ArrayBuffer} value - The value of the input data.
* @param {number} type - The data type of value, see Dish enums.
*/
Dish.prototype.set = function(value, type) {
log.debug("Dish type: " + Dish.enumLookup(type));
this.value = value;
this.type = type;
@@ -114,7 +126,7 @@ Dish.prototype.set = function(value, type) {
* Returns the value of the data in the type format specified.
*
* @param {number} type - The data type of value, see Dish enums.
* @returns {byteArray|string|number} The value of the output data.
* @returns {byteArray|string|number|ArrayBuffer} The value of the output data.
*/
Dish.prototype.get = function(type) {
if (this.type !== type) {
@@ -130,24 +142,29 @@ Dish.prototype.get = function(type) {
* @param {number} toType - The data type of value, see Dish enums.
*/
Dish.prototype.translate = function(toType) {
log.debug(`Translating Dish from ${Dish.enumLookup(this.type)} to ${Dish.enumLookup(toType)}`);
// Convert data to intermediate byteArray type
switch (this.type) {
case Dish.STRING:
this.value = this.value ? Utils.strToByteArray(this.value) : [];
this.type = Dish.BYTE_ARRAY;
break;
case Dish.NUMBER:
this.value = typeof this.value == "number" ? Utils.strToByteArray(this.value.toString()) : [];
this.type = Dish.BYTE_ARRAY;
break;
case Dish.HTML:
this.value = this.value ? Utils.strToByteArray(Utils.unescapeHtml(Utils.stripHtmlTags(this.value, true))) : [];
this.type = Dish.BYTE_ARRAY;
break;
case Dish.ARRAY_BUFFER:
// Array.from() would be nicer here, but it's slightly slower
this.value = Array.prototype.slice.call(new Uint8Array(this.value));
break;
default:
break;
}
this.type = Dish.BYTE_ARRAY;
// Convert from byteArray to toType
switch (toType) {
case Dish.STRING:
@@ -159,6 +176,10 @@ Dish.prototype.translate = function(toType) {
this.value = this.value ? parseFloat(Utils.byteArrayToUtf8(this.value)) : 0;
this.type = Dish.NUMBER;
break;
case Dish.ARRAY_BUFFER:
this.value = new Uint8Array(this.value).buffer;
this.type = Dish.ARRAY_BUFFER;
break;
default:
break;
}
@@ -180,7 +201,7 @@ Dish.prototype.valid = function() {
// Check that every value is a number between 0 - 255
for (let i = 0; i < this.value.length; i++) {
if (typeof this.value[i] != "number" ||
if (typeof this.value[i] !== "number" ||
this.value[i] < 0 ||
this.value[i] > 255) {
return false;
@@ -189,18 +210,38 @@ Dish.prototype.valid = function() {
return true;
case Dish.STRING:
case Dish.HTML:
if (typeof this.value == "string") {
return true;
}
return false;
return typeof this.value === "string";
case Dish.NUMBER:
if (typeof this.value == "number") {
return true;
}
return false;
return typeof this.value === "number";
case Dish.ARRAY_BUFFER:
return this.value instanceof ArrayBuffer;
default:
return false;
}
};
/**
* Determines how much space the Dish takes up.
* Numbers in JavaScript are 64-bit floating point, however for the purposes of the Dish,
* we measure how many bytes are taken up when the number is written as a string.
*
* @returns {number}
*/
Dish.prototype.size = function() {
switch (this.type) {
case Dish.BYTE_ARRAY:
case Dish.STRING:
case Dish.HTML:
return this.value.length;
case Dish.NUMBER:
return this.value.toString().length;
case Dish.ARRAY_BUFFER:
return this.value.byteLength;
default:
return -1;
}
};
export default Dish;

View File

@@ -56,6 +56,7 @@ const FlowControl = {
// Run recipe over each tranche
for (i = 0; i < inputs.length; i++) {
log.debug(`Entering tranche ${i + 1} of ${inputs.length}`);
const dish = new Dish(inputs[i], inputType);
try {
progress = await recipe.execute(dish, 0);
@@ -169,16 +170,19 @@ const FlowControl = {
* @returns {Object} The updated state of the recipe.
*/
runJump: function(state) {
let ings = state.opList[state.progress].getIngValues(),
jmpIndex = FlowControl._getLabelIndex(ings[0], state),
maxJumps = ings[1];
const ings = state.opList[state.progress].getIngValues(),
label = ings[0],
maxJumps = ings[1],
jmpIndex = FlowControl._getLabelIndex(label, state);
if (state.numJumps >= maxJumps || jmpIndex === -1) {
log.debug("Maximum jumps reached or label cannot be found");
return state;
}
state.progress = jmpIndex;
state.numJumps++;
log.debug(`Jumping to label '${label}' at position ${jmpIndex} (jumps = ${state.numJumps})`);
return state;
},
@@ -194,14 +198,16 @@ const FlowControl = {
* @returns {Object} The updated state of the recipe.
*/
runCondJump: function(state) {
let ings = state.opList[state.progress].getIngValues(),
const ings = state.opList[state.progress].getIngValues(),
dish = state.dish,
regexStr = ings[0],
invert = ings[1],
jmpIndex = FlowControl._getLabelIndex(ings[2], state),
maxJumps = ings[3];
label = ings[2],
maxJumps = ings[3],
jmpIndex = FlowControl._getLabelIndex(label, state);
if (state.numJumps >= maxJumps || jmpIndex === -1) {
log.debug("Maximum jumps reached or label cannot be found");
return state;
}
@@ -210,6 +216,7 @@ const FlowControl = {
if (!invert && strMatch || invert && !strMatch) {
state.progress = jmpIndex;
state.numJumps++;
log.debug(`Jumping to label '${label}' at position ${jmpIndex} (jumps = ${state.numJumps})`);
}
}
@@ -249,6 +256,7 @@ const FlowControl = {
/**
* Returns the index of a label.
*
* @private
* @param {Object} state
* @param {string} name
* @returns {number}

View File

@@ -146,18 +146,23 @@ Recipe.prototype.lastOpIndex = function(startIndex) {
Recipe.prototype.execute = async function(dish, startFrom) {
startFrom = startFrom || 0;
let op, input, output, numJumps = 0, numRegisters = 0;
log.debug(`[*] Executing recipe of ${this.opList.length} operations, starting at ${startFrom}`);
for (let i = startFrom; i < this.opList.length; i++) {
op = this.opList[i];
log.debug(`[${i}] ${op.name} ${JSON.stringify(op.getIngValues())}`);
if (op.isDisabled()) {
log.debug("Operation is disabled, skipping");
continue;
}
if (op.isBreakpoint()) {
log.debug("Pausing at breakpoint");
return i;
}
try {
input = dish.get(op.inputType);
log.debug("Executing operation");
if (op.isFlowControl()) {
// Package up the current state
@@ -193,6 +198,7 @@ Recipe.prototype.execute = async function(dish, startFrom) {
}
}
log.debug("Recipe complete");
return this.opList.length;
};

View File

@@ -1,4 +1,4 @@
import CryptoJS from "crypto-js";
import utf8 from "utf8";
/**
@@ -64,58 +64,6 @@ const Utils = {
},
/**
* Adds leading zeros to strings
*
* @param {string} str - String to add leading characters to.
* @param {number} max - Maximum width of the string.
* @param {char} [chr='0'] - The character to pad with.
* @returns {string}
*
* @example
* // returns "0a"
* Utils.padLeft("a", 2);
*
* // returns "000a"
* Utils.padLeft("a", 4);
*
* // returns "xxxa"
* Utils.padLeft("a", 4, "x");
*
* // returns "bcabchello"
* Utils.padLeft("hello", 10, "abc");
*/
padLeft: function(str, max, chr) {
chr = chr || "0";
let startIndex = chr.length - (max - str.length);
startIndex = startIndex < 0 ? 0 : startIndex;
return str.length < max ?
Utils.padLeft(chr.slice(startIndex, chr.length) + str, max, chr) : str;
},
/**
* Adds trailing spaces to strings.
*
* @param {string} str - String to add trailing characters to.
* @param {number} max - Maximum width of the string.
* @param {char} [chr='0'] - The character to pad with.
* @returns {string}
*
* @example
* // returns "a "
* Utils.padRight("a", 4);
*
* // returns "axxx"
* Utils.padRight("a", 4, "x");
*/
padRight: function(str, max, chr) {
chr = chr || " ";
return str.length < max ?
Utils.padRight(str + chr.slice(0, max-str.length), max, chr) : str;
},
/**
* Adds trailing bytes to a byteArray.
*
@@ -152,14 +100,6 @@ const Utils = {
},
/**
* @alias Utils.padLeft
*/
pad: function(str, max, chr) {
return Utils.padLeft(str, max, chr);
},
/**
* Truncates a long string to max length and adds suffix.
*
@@ -201,7 +141,7 @@ const Utils = {
hex: function(c, length) {
c = typeof c == "string" ? Utils.ord(c) : c;
length = length || 2;
return Utils.pad(c.toString(16), length);
return c.toString(16).padStart(length, "0");
},
@@ -222,7 +162,7 @@ const Utils = {
bin: function(c, length) {
c = typeof c == "string" ? Utils.ord(c) : c;
length = length || 8;
return Utils.pad(c.toString(2), length);
return c.toString(2).padStart(length, "0");
},
@@ -261,7 +201,7 @@ const Utils = {
* Utils.parseEscapedChars("\\n");
*/
parseEscapedChars: function(str) {
return str.replace(/(\\)?\\([nrtbf]|x[\da-f]{2})/g, function(m, a, b) {
return str.replace(/(\\)?\\([nrtbf]|x[\da-fA-F]{2})/g, function(m, a, b) {
if (a === "\\") return "\\"+b;
switch (b[0]) {
case "n":
@@ -340,6 +280,72 @@ const Utils = {
},
/**
* Coverts data of varying types to a byteArray.
* Accepts hex, Base64, UTF8 and Latin1 strings.
*
* @param {string} str
* @param {string} type - One of "Hex", "Base64", "UTF8" or "Latin1"
* @returns {byteArray}
*
* @example
* // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130]
* Utils.convertToByteArray("Привет", "utf8");
*
* // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130]
* Utils.convertToByteArray("d097d0b4d180d0b0d0b2d181d182d0b2d183d0b9d182d0b5", "hex");
*
* // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130]
* Utils.convertToByteArray("0JfQtNGA0LDQstGB0YLQstGD0LnRgtC1", "base64");
*/
convertToByteArray: function(str, type) {
switch (type.toLowerCase()) {
case "hex":
return Utils.fromHex(str);
case "base64":
return Utils.fromBase64(str, null, "byteArray");
case "utf8":
return Utils.strToUtf8ByteArray(str);
case "latin1":
default:
return Utils.strToByteArray(str);
}
},
/**
* Coverts data of varying types to a byte string.
* Accepts hex, Base64, UTF8 and Latin1 strings.
*
* @param {string} str
* @param {string} type - One of "Hex", "Base64", "UTF8" or "Latin1"
* @returns {string}
*
* @example
* // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130]
* Utils.convertToByteArray("Привет", "utf8");
*
* // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130]
* Utils.convertToByteArray("d097d0b4d180d0b0d0b2d181d182d0b2d183d0b9d182d0b5", "hex");
*
* // returns [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130]
* Utils.convertToByteArray("0JfQtNGA0LDQstGB0YLQstGD0LnRgtC1", "base64");
*/
convertToByteString: function(str, type) {
switch (type.toLowerCase()) {
case "hex":
return Utils.byteArrayToChars(Utils.fromHex(str));
case "base64":
return Utils.byteArrayToChars(Utils.fromBase64(str, null, "byteArray"));
case "utf8":
return utf8.encode(str);
case "latin1":
default:
return str;
}
},
/**
* Converts a string to a byte array.
* Treats the string as UTF-8 if any values are over 255.
@@ -381,17 +387,17 @@ const Utils = {
* Utils.strToUtf8ByteArray("你好");
*/
strToUtf8ByteArray: function(str) {
let wordArray = CryptoJS.enc.Utf8.parse(str),
byteArray = Utils.wordArrayToByteArray(wordArray);
const utf8Str = utf8.encode(str);
if (str.length !== wordArray.sigBytes) {
if (str.length !== utf8Str.length) {
if (ENVIRONMENT_IS_WORKER()) {
self.setOption("attemptHighlight", false);
} else if (ENVIRONMENT_IS_WEB()) {
window.app.options.attemptHighlight = false;
}
}
return byteArray;
return Utils.strToByteArray(utf8Str);
},
@@ -443,26 +449,21 @@ const Utils = {
* Utils.byteArrayToUtf8([228,189,160,229,165,189]);
*/
byteArrayToUtf8: function(byteArray) {
const str = Utils.byteArrayToChars(byteArray);
try {
// Try to output data as UTF-8 string
const words = [];
for (let i = 0; i < byteArray.length; i++) {
words[i >>> 2] |= byteArray[i] << (24 - (i % 4) * 8);
}
let wordArray = new CryptoJS.lib.WordArray.init(words, byteArray.length),
str = CryptoJS.enc.Utf8.stringify(wordArray);
const utf8Str = utf8.decode(str);
if (str.length !== wordArray.sigBytes) {
if (str.length !== utf8Str.length) {
if (ENVIRONMENT_IS_WORKER()) {
self.setOption("attemptHighlight", false);
} else if (ENVIRONMENT_IS_WEB()) {
window.app.options.attemptHighlight = false;
}
}
return str;
return utf8Str;
} catch (err) {
// If it fails, treat it as ANSI
return Utils.byteArrayToChars(byteArray);
return str;
}
},
@@ -470,7 +471,7 @@ const Utils = {
/**
* Converts a charcode array to a string.
*
* @param {byteArray} byteArray
* @param {byteArray|Uint8Array} byteArray
* @returns {string}
*
* @example
@@ -491,33 +492,25 @@ const Utils = {
/**
* Converts a CryptoJS.lib.WordArray to a byteArray.
* Converts an ArrayBuffer to a string.
*
* @param {CryptoJS.lib.WordArray} wordArray
* @returns {byteArray}
* @param {ArrayBuffer} arrayBuffer
* @returns {string}
*
* @example
* // returns [84, 101, 115, 116]
* Utils.wordArrayToByteArray(CryptoJS.enc.Hex.parse("54657374"));
* // returns "hello"
* Utils.arrayBufferToStr(Uint8Array.from([104,101,108,108,111]).buffer);
*/
wordArrayToByteArray: function(wordArray) {
if (wordArray.sigBytes <= 0) return [];
let words = wordArray.words,
byteArray = [];
for (let i = 0; i < wordArray.sigBytes; i++) {
byteArray.push((words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff);
}
return byteArray;
arrayBufferToStr: function(arrayBuffer) {
const byteArray = Array.prototype.slice.call(new Uint8Array(arrayBuffer));
return Utils.byteArrayToUtf8(byteArray);
},
/**
* Base64's the input byte array using the given alphabet, returning a string.
*
* @param {byteArray|string} data
* @param {byteArray|Uint8Array|string} data
* @param {string} [alphabet]
* @returns {string}
*
@@ -636,7 +629,7 @@ const Utils = {
/**
* Convert a byte array into a hex string.
*
* @param {byteArray} data
* @param {Uint8Array|byteArray} data
* @param {string} [delim=" "]
* @param {number} [padding=2]
* @returns {string}
@@ -656,7 +649,7 @@ const Utils = {
let output = "";
for (let i = 0; i < data.length; i++) {
output += Utils.pad(data[i].toString(16), padding) + delim;
output += data[i].toString(16).padStart(padding, "0") + delim;
}
// Add \x or 0x to beginning
@@ -1248,21 +1241,6 @@ const Utils = {
"None": /\s+/g // Included here to remove whitespace when there shouldn't be any
},
/**
* A mapping of string formats to their classes in the CryptoJS library.
* @constant
*/
format: {
"Hex": CryptoJS.enc.Hex,
"Base64": CryptoJS.enc.Base64,
"UTF8": CryptoJS.enc.Utf8,
"UTF16": CryptoJS.enc.Utf16,
"UTF16LE": CryptoJS.enc.Utf16LE,
"UTF16BE": CryptoJS.enc.Utf16BE,
"Latin1": CryptoJS.enc.Latin1,
},
};
export default Utils;
@@ -1376,29 +1354,43 @@ String.prototype.count = function(chr) {
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// Library overrides ///////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Override for the CryptoJS Hex encoding parser to remove whitespace before attempting to parse
* the hex string.
*
* @param {string} hexStr
* @returns {CryptoJS.lib.WordArray}
/*
* Polyfills
*/
CryptoJS.enc.Hex.parse = function (hexStr) {
// Remove whitespace
hexStr = hexStr.replace(/\s/g, "");
// Shortcut
const hexStrLength = hexStr.length;
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
if (!String.prototype.padStart) {
String.prototype.padStart = function padStart(targetLength, padString) {
targetLength = targetLength>>0; //floor if number or convert non-number to 0;
padString = String((typeof padString !== "undefined" ? padString : " "));
if (this.length > targetLength) {
return String(this);
} else {
targetLength = targetLength-this.length;
if (targetLength > padString.length) {
padString += padString.repeat(targetLength/padString.length); //append to original to ensure we are longer than needed
}
return padString.slice(0, targetLength) + String(this);
}
};
}
// Convert
const words = [];
for (let i = 0; i < hexStrLength; i += 2) {
words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4);
}
return new CryptoJS.lib.WordArray.init(words, hexStrLength / 2);
};
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd
if (!String.prototype.padEnd) {
String.prototype.padEnd = function padEnd(targetLength, padString) {
targetLength = targetLength>>0; //floor if number or convert non-number to 0;
padString = String((typeof padString !== "undefined" ? padString : " "));
if (this.length > targetLength) {
return String(this);
} else {
targetLength = targetLength-this.length;
if (targetLength > padString.length) {
padString += padString.repeat(targetLength/padString.length); //append to original to ensure we are longer than needed
}
return String(this) + padString.slice(0, targetLength);
}
};
}

View File

@@ -79,8 +79,8 @@ const Categories = [
"DES Decrypt",
"Triple DES Encrypt",
"Triple DES Decrypt",
"Rabbit Encrypt",
"Rabbit Decrypt",
"RC2 Encrypt",
"RC2 Decrypt",
"RC4",
"RC4 Drop",
"ROT13",
@@ -99,6 +99,7 @@ const Categories = [
"Substitute",
"Derive PBKDF2 key",
"Derive EVP key",
"Pseudo-Random Number Generator",
]
},
{
@@ -198,6 +199,7 @@ const Categories = [
"Parse colour code",
"Escape string",
"Unescape string",
"Pseudo-Random Number Generator",
]
},
{
@@ -313,6 +315,7 @@ const Categories = [
"Detect File Type",
"Scan for Embedded Files",
"Disassemble x86",
"Pseudo-Random Number Generator",
"Generate UUID",
"Generate TOTP",
"Generate HOTP",

View File

@@ -245,7 +245,7 @@ const OperationConfig = {
description: "Base64 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.<br><br>This operation encodes data in an ASCII Base64 string.<br><br>e.g. <code>hello</code> becomes <code>aGVsbG8=</code>",
highlight: "func",
highlightReverse: "func",
inputType: "byteArray",
inputType: "ArrayBuffer",
outputType: "string",
args: [
{
@@ -631,7 +631,7 @@ const OperationConfig = {
description: "Converts the input string to hexadecimal bytes separated by the specified delimiter.<br><br>e.g. The UTF-8 encoded string <code>Γειά σου</code> becomes <code>ce 93 ce b5 ce b9 ce ac 20 cf 83 ce bf cf 85 0a</code>",
highlight: "func",
highlightReverse: "func",
inputType: "byteArray",
inputType: "ArrayBuffer",
outputType: "string",
args: [
{
@@ -782,7 +782,7 @@ const OperationConfig = {
description: "Creates a hexdump of the input data, displaying both the hexadecimal values of each byte and an ASCII representation alongside.",
highlight: "func",
highlightReverse: "func",
inputType: "byteArray",
inputType: "ArrayBuffer",
outputType: "string",
args: [
{
@@ -1102,287 +1102,7 @@ const OperationConfig = {
},
"AES Decrypt": {
module: "Ciphers",
description: "To successfully decrypt AES, you need either:<ul><li>The passphrase</li><li>Or the key and IV</li></ul>The IV should be the first 16 bytes of encrypted material.",
inputType: "string",
outputType: "string",
args: [
{
name: "Passphrase/Key",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT2
},
{
name: "IV",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Salt",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Mode",
type: "option",
value: Cipher.MODES
},
{
name: "Padding",
type: "option",
value: Cipher.PADDING
},
{
name: "Input format",
type: "option",
value: Cipher.IO_FORMAT1
},
{
name: "Output format",
type: "option",
value: Cipher.IO_FORMAT2
},
]
},
"AES Encrypt": {
module: "Ciphers",
description: "Input: Either enter a passphrase (which will be used to derive a key using the OpenSSL KDF) or both the key and IV.<br><br>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>AES-128, AES-192, and AES-256 are supported. The variant will be chosen based on the size of the key passed in. If a passphrase is used, a 256-bit key will be generated.",
inputType: "string",
outputType: "string",
args: [
{
name: "Passphrase/Key",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT2
},
{
name: "IV",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Salt",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Mode",
type: "option",
value: Cipher.MODES
},
{
name: "Padding",
type: "option",
value: Cipher.PADDING
},
{
name: "Output result",
type: "option",
value: Cipher.RESULT_TYPE
},
{
name: "Output format",
type: "option",
value: Cipher.IO_FORMAT1
},
]
},
"DES Decrypt": {
module: "Ciphers",
description: "To successfully decrypt DES, you need either:<ul><li>The passphrase</li><li>Or the key and IV</li></ul>The IV should be the first 8 bytes of encrypted material.",
inputType: "string",
outputType: "string",
args: [
{
name: "Passphrase/Key",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT2
},
{
name: "IV",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Salt",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Mode",
type: "option",
value: Cipher.MODES
},
{
name: "Padding",
type: "option",
value: Cipher.PADDING
},
{
name: "Input format",
type: "option",
value: Cipher.IO_FORMAT1
},
{
name: "Output format",
type: "option",
value: Cipher.IO_FORMAT2
},
]
},
"DES Encrypt": {
module: "Ciphers",
description: "Input: Either enter a passphrase (which will be used to derive a key using the OpenSSL KDF) or both the key and IV.<br><br>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.",
inputType: "string",
outputType: "string",
args: [
{
name: "Passphrase/Key",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT2
},
{
name: "IV",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Salt",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Mode",
type: "option",
value: Cipher.MODES
},
{
name: "Padding",
type: "option",
value: Cipher.PADDING
},
{
name: "Output result",
type: "option",
value: Cipher.RESULT_TYPE
},
{
name: "Output format",
type: "option",
value: Cipher.IO_FORMAT1
},
]
},
"Triple DES Decrypt": {
module: "Ciphers",
description: "To successfully decrypt Triple DES, you need either:<ul><li>The passphrase</li><li>Or the key and IV</li></ul>The IV should be the first 8 bytes of encrypted material.",
inputType: "string",
outputType: "string",
args: [
{
name: "Passphrase/Key",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT2
},
{
name: "IV",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Salt",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Mode",
type: "option",
value: Cipher.MODES
},
{
name: "Padding",
type: "option",
value: Cipher.PADDING
},
{
name: "Input format",
type: "option",
value: Cipher.IO_FORMAT1
},
{
name: "Output format",
type: "option",
value: Cipher.IO_FORMAT2
},
]
},
"Triple DES Encrypt": {
module: "Ciphers",
description: "Input: Either enter a passphrase (which will be used to derive a key using the OpenSSL KDF) or both the key and IV.<br><br>Triple DES applies DES three times to each block to increase key size.",
inputType: "string",
outputType: "string",
args: [
{
name: "Passphrase/Key",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT2
},
{
name: "IV",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Salt",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Mode",
type: "option",
value: Cipher.MODES
},
{
name: "Padding",
type: "option",
value: Cipher.PADDING
},
{
name: "Output result",
type: "option",
value: Cipher.RESULT_TYPE
},
{
name: "Output format",
type: "option",
value: Cipher.IO_FORMAT1
},
]
},
"Blowfish Decrypt": {
module: "Ciphers",
description: "Blowfish is a symmetric-key block cipher designed in 1993 by Bruce Schneier and included in a large number of cipher suites and encryption products. AES now receives more attention.",
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.",
inputType: "string",
outputType: "string",
args: [
@@ -1390,7 +1110,229 @@ const OperationConfig = {
name: "Key",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT2
toggleValues: Cipher.IO_FORMAT1
},
{
name: "IV",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Mode",
type: "option",
value: Cipher.AES_MODES
},
{
name: "Input",
type: "option",
value: Cipher.IO_FORMAT4
},
{
name: "Output",
type: "option",
value: Cipher.IO_FORMAT3
},
{
name: "GCM Tag",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
]
},
"AES Encrypt": {
module: "Ciphers",
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>You can generate a password-based key using one of the KDF operations.<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.",
inputType: "string",
outputType: "string",
args: [
{
name: "Key",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "IV",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Mode",
type: "option",
value: Cipher.AES_MODES
},
{
name: "Input",
type: "option",
value: Cipher.IO_FORMAT3
},
{
name: "Output",
type: "option",
value: Cipher.IO_FORMAT4
},
]
},
"DES Decrypt": {
module: "Ciphers",
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.",
inputType: "string",
outputType: "string",
args: [
{
name: "Key",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "IV",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Mode",
type: "option",
value: Cipher.DES_MODES
},
{
name: "Input",
type: "option",
value: Cipher.IO_FORMAT4
},
{
name: "Output",
type: "option",
value: Cipher.IO_FORMAT3
},
]
},
"DES Encrypt": {
module: "Ciphers",
description: "DES is a previously dominant algorithm for encryption, and was published as an official U.S. Federal Information Processing Standard (FIPS). It is now considered to be insecure due to its small key size.<br><br><b>Key:</b> DES uses a key length of 8 bytes (64 bits).<br>Triple DES uses a key length of 24 bytes (192 bits).<br><br>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.",
inputType: "string",
outputType: "string",
args: [
{
name: "Key",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "IV",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Mode",
type: "option",
value: Cipher.DES_MODES
},
{
name: "Input",
type: "option",
value: Cipher.IO_FORMAT3
},
{
name: "Output",
type: "option",
value: Cipher.IO_FORMAT4
},
]
},
"Triple DES Decrypt": {
module: "Ciphers",
description: "Triple DES applies DES three times to each block to increase key size.<br><br><b>Key:</b> Triple DES uses a key length of 24 bytes (192 bits).<br>DES uses a key length of 8 bytes (64 bits).<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.",
inputType: "string",
outputType: "string",
args: [
{
name: "Key",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "IV",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Mode",
type: "option",
value: Cipher.DES_MODES
},
{
name: "Input",
type: "option",
value: Cipher.IO_FORMAT4
},
{
name: "Output",
type: "option",
value: Cipher.IO_FORMAT3
},
]
},
"Triple DES Encrypt": {
module: "Ciphers",
description: "Triple DES applies DES three times to each block to increase key size.<br><br><b>Key:</b> Triple DES uses a key length of 24 bytes (192 bits).<br>DES uses a key length of 8 bytes (64 bits).<br><br>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.<br><br><b>Padding:</b> In CBC and ECB mode, PKCS#7 padding will be used.",
inputType: "string",
outputType: "string",
args: [
{
name: "Key",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "IV",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Mode",
type: "option",
value: Cipher.DES_MODES
},
{
name: "Input",
type: "option",
value: Cipher.IO_FORMAT3
},
{
name: "Output",
type: "option",
value: Cipher.IO_FORMAT4
},
]
},
"Blowfish Decrypt": {
module: "Ciphers",
description: "Blowfish is a symmetric-key block cipher designed in 1993 by Bruce Schneier and included in a large number of cipher suites and encryption products. AES now receives more attention.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.",
inputType: "string",
outputType: "string",
args: [
{
name: "Key",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "IV",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Mode",
@@ -1398,7 +1340,12 @@ const OperationConfig = {
value: Cipher.BLOWFISH_MODES
},
{
name: "Input format",
name: "Input",
type: "option",
value: Cipher.BLOWFISH_OUTPUT_TYPES
},
{
name: "Output",
type: "option",
value: Cipher.IO_FORMAT3
},
@@ -1406,7 +1353,7 @@ const OperationConfig = {
},
"Blowfish Encrypt": {
module: "Ciphers",
description: "Blowfish is a symmetric-key block cipher designed in 1993 by Bruce Schneier and included in a large number of cipher suites and encryption products. AES now receives more attention.",
description: "Blowfish is a symmetric-key block cipher designed in 1993 by Bruce Schneier and included in a large number of cipher suites and encryption products. AES now receives more attention.<br><br><b>IV:</b> The Initialization Vector should be 8 bytes long. If not entered, it will default to 8 null bytes.",
inputType: "string",
outputType: "string",
args: [
@@ -1414,7 +1361,13 @@ const OperationConfig = {
name: "Key",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT2
toggleValues: Cipher.IO_FORMAT1
},
{
name: "IV",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Mode",
@@ -1422,109 +1375,20 @@ const OperationConfig = {
value: Cipher.BLOWFISH_MODES
},
{
name: "Output format",
name: "Input",
type: "option",
value: Cipher.IO_FORMAT3
},
]
},
"Rabbit Decrypt": {
module: "Ciphers",
description: "To successfully decrypt Rabbit, you need either:<ul><li>The passphrase</li><li>Or the key and IV (This is currently broken. You need the key and salt at the moment.)</li></ul>The IV should be the first 8 bytes of encrypted material.",
inputType: "string",
outputType: "string",
args: [
{
name: "Passphrase/Key",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT2
},
{
name: "IV",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Salt",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Mode",
name: "Output",
type: "option",
value: Cipher.MODES
},
{
name: "Padding",
type: "option",
value: Cipher.PADDING
},
{
name: "Input format",
type: "option",
value: Cipher.IO_FORMAT1
},
{
name: "Output format",
type: "option",
value: Cipher.IO_FORMAT2
},
]
},
"Rabbit Encrypt": {
module: "Ciphers",
description: "Input: Either enter a passphrase (which will be used to derive a key using the OpenSSL KDF) or both the key and IV.<br><br>Rabbit is a high-performance stream cipher and a finalist in the eSTREAM Portfolio. It is one of the four designs selected after a 3 1/2 year process where 22 designs were evaluated.",
inputType: "string",
outputType: "string",
args: [
{
name: "Passphrase/Key",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT2
},
{
name: "IV",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Salt",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Mode",
type: "option",
value: Cipher.MODES
},
{
name: "Padding",
type: "option",
value: Cipher.PADDING
},
{
name: "Output result",
type: "option",
value: Cipher.RESULT_TYPE
},
{
name: "Output format",
type: "option",
value: Cipher.IO_FORMAT1
value: Cipher.BLOWFISH_OUTPUT_TYPES
},
]
},
"RC4": {
module: "Ciphers",
description: "RC4 is a widely-used stream cipher. It is used in popular protocols such as SSL and WEP. Although remarkable for its simplicity and speed, the algorithm's history doesn't inspire confidence in its security.",
description: "RC4 (also known as ARC4) is a widely-used stream cipher designed by Ron Rivest. It is used in popular protocols such as SSL and WEP. Although remarkable for its simplicity and speed, the algorithm's history doesn't inspire confidence in its security.",
highlight: true,
highlightReverse: true,
inputType: "string",
@@ -1534,17 +1398,17 @@ const OperationConfig = {
name: "Passphrase",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT2
toggleValues: Cipher.RC4_KEY_FORMAT
},
{
name: "Input format",
type: "option",
value: Cipher.IO_FORMAT4
value: Cipher.CJS_IO_FORMAT
},
{
name: "Output format",
type: "option",
value: Cipher.IO_FORMAT4
value: Cipher.CJS_IO_FORMAT
},
]
},
@@ -1560,17 +1424,17 @@ const OperationConfig = {
name: "Passphrase",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT2
toggleValues: Cipher.RC4_KEY_FORMAT
},
{
name: "Input format",
type: "option",
value: Cipher.IO_FORMAT4
value: Cipher.CJS_IO_FORMAT
},
{
name: "Output format",
type: "option",
value: Cipher.IO_FORMAT4
value: Cipher.CJS_IO_FORMAT
},
{
name: "Number of bytes to drop",
@@ -1579,50 +1443,96 @@ const OperationConfig = {
},
]
},
"Derive PBKDF2 key": {
"RC2 Decrypt": {
module: "Ciphers",
description: "PBKDF2 is a password-based key derivation function. In many applications of cryptography, user security is ultimately dependent on a password, and because a password usually can't be used directly as a cryptographic key, some processing is required.<br><br>A salt provides a large set of keys for any given password, and an iteration count increases the cost of producing keys from a password, thereby also increasing the difficulty of attack.<br><br>Enter your passphrase as the input and then set the relevant options to generate a key.",
description: "RC2 (also known as ARC2) is a symmetric-key block cipher designed by Ron Rivest in 1987. 'RC' stands for 'Rivest Cipher'.<br><br><b>Key:</b> RC2 uses a variable size key.<br><br><b>IV:</b> To run the cipher in CBC mode, the Initialization Vector should be 8 bytes long. If the IV is left blank, the cipher will run in ECB mode.<br><br><b>Padding:</b> In both CBC and ECB mode, PKCS#7 padding will be used.",
inputType: "string",
outputType: "string",
args: [
{
name: "Key size",
type: "number",
value: Cipher.KDF_KEY_SIZE
name: "Key",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Iterations",
type: "number",
value: Cipher.KDF_ITERATIONS
name: "IV",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Hashing function",
name: "Input",
type: "option",
value: Cipher.HASHERS
value: Cipher.IO_FORMAT4
},
{
name: "Salt (hex)",
type: "string",
value: ""
},
{
name: "Input format",
type: "option",
value: Cipher.IO_FORMAT2
},
{
name: "Output format",
name: "Output",
type: "option",
value: Cipher.IO_FORMAT3
},
]
},
"Derive EVP key": {
"RC2 Encrypt": {
module: "Ciphers",
description: "EVP is a password-based key derivation function used extensively in OpenSSL. In many applications of cryptography, user security is ultimately dependent on a password, and because a password usually can't be used directly as a cryptographic key, some processing is required.<br><br>A salt provides a large set of keys for any given password, and an iteration count increases the cost of producing keys from a password, thereby also increasing the difficulty of attack.<br><br>Enter your passphrase as the input and then set the relevant options to generate a key.",
description: "RC2 (also known as ARC2) is a symmetric-key block cipher designed by Ron Rivest in 1987. 'RC' stands for 'Rivest Cipher'.<br><br><b>Key:</b> RC2 uses a variable size key.<br><br>You can generate a password-based key using one of the KDF operations.<br><br><b>IV:</b> To run the cipher in CBC mode, the Initialization Vector should be 8 bytes long. If the IV is left blank, the cipher will run in ECB mode.<br><br><b>Padding:</b> In both CBC and ECB mode, PKCS#7 padding will be used.",
inputType: "string",
outputType: "string",
args: [
{
name: "Key",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "IV",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
{
name: "Input",
type: "option",
value: Cipher.IO_FORMAT3
},
{
name: "Output",
type: "option",
value: Cipher.IO_FORMAT4
},
]
},
"Pseudo-Random Number Generator": {
module: "Ciphers",
description: "A cryptographically-secure pseudo-random number generator (PRNG).<br><br>This operation uses the browser's built-in <code>crypto.getRandomValues()</code> method if available. If this cannot be found, it falls back to a Fortuna-based PRNG algorithm.",
inputType: "string",
outputType: "string",
args: [
{
name: "Number of bytes",
type: "number",
value: Cipher.PRNG_BYTES
},
{
name: "Output as",
type: "option",
value: Cipher.PRNG_OUTPUT
}
]
},
"Derive PBKDF2 key": {
module: "Ciphers",
description: "PBKDF2 is a password-based key derivation function. It is part of RSA Laboratories' Public-Key Cryptography Standards (PKCS) series, specifically PKCS #5 v2.0, also published as Internet Engineering Task Force's RFC 2898.<br><br>In many applications of cryptography, user security is ultimately dependent on a password, and because a password usually can't be used directly as a cryptographic key, some processing is required.<br><br>A salt provides a large set of keys for any given password, and an iteration count increases the cost of producing keys from a password, thereby also increasing the difficulty of attack.<br><br>If you leave the salt argument empty, a random salt will be generated.",
inputType: "string",
outputType: "string",
args: [
{
name: "Passphrase",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT2
},
{
name: "Key size",
type: "number",
@@ -1639,19 +1549,45 @@ const OperationConfig = {
value: Cipher.HASHERS
},
{
name: "Salt (hex)",
type: "string",
value: ""
name: "Salt",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
]
},
"Derive EVP key": {
module: "Ciphers",
description: "EVP is a password-based key derivation function (PBKDF) used extensively in OpenSSL. In many applications of cryptography, user security is ultimately dependent on a password, and because a password usually can't be used directly as a cryptographic key, some processing is required.<br><br>A salt provides a large set of keys for any given password, and an iteration count increases the cost of producing keys from a password, thereby also increasing the difficulty of attack.<br><br>If you leave the salt argument empty, a random salt will be generated.",
inputType: "string",
outputType: "string",
args: [
{
name: "Passphrase",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT2
},
{
name: "Input format",
type: "option",
value: Cipher.IO_FORMAT2
name: "Key size",
type: "number",
value: Cipher.KDF_KEY_SIZE
},
{
name: "Output format",
name: "Iterations",
type: "number",
value: Cipher.KDF_ITERATIONS
},
{
name: "Hashing function",
type: "option",
value: Cipher.IO_FORMAT3
value: Cipher.HASHERS
},
{
name: "Salt",
type: "toggleString",
value: "",
toggleValues: Cipher.IO_FORMAT1
},
]
},
@@ -1977,9 +1913,9 @@ const OperationConfig = {
},
"Drop bytes": {
module: "Default",
description: "Cuts the specified number of bytes out of the data.",
inputType: "byteArray",
outputType: "byteArray",
description: "Cuts a slice of the specified number of bytes out of the data.",
inputType: "ArrayBuffer",
outputType: "ArrayBuffer",
args: [
{
name: "Start",
@@ -2001,8 +1937,8 @@ const OperationConfig = {
"Take bytes": {
module: "Default",
description: "Takes a slice of the specified number of bytes from the data.",
inputType: "byteArray",
outputType: "byteArray",
inputType: "ArrayBuffer",
outputType: "ArrayBuffer",
args: [
{
name: "Start",
@@ -3287,7 +3223,7 @@ const OperationConfig = {
"Frequency distribution": {
module: "Default",
description: "Displays the distribution of bytes in the data as a graph.",
inputType: "byteArray",
inputType: "ArrayBuffer",
outputType: "html",
args: [
{
@@ -3300,7 +3236,7 @@ const OperationConfig = {
"Chi Square": {
module: "Default",
description: "Calculates the Chi Square distribution of values.",
inputType: "byteArray",
inputType: "ArrayBuffer",
outputType: "number",
args: []
},
@@ -3379,14 +3315,14 @@ const OperationConfig = {
"Detect File Type": {
module: "Default",
description: "Attempts to guess the MIME (Multipurpose Internet Mail Extensions) type of the data based on 'magic bytes'.<br><br>Currently supports the following file types: 7z, amr, avi, bmp, bz2, class, cr2, crx, dex, dmg, doc, elf, eot, epub, exe, flac, flv, gif, gz, ico, iso, jpg, jxr, m4a, m4v, mid, mkv, mov, mp3, mp4, mpg, ogg, otf, pdf, png, ppt, ps, psd, rar, rtf, sqlite, swf, tar, tar.z, tif, ttf, utf8, vmdk, wav, webm, webp, wmv, woff, woff2, xls, xz, zip.",
inputType: "byteArray",
inputType: "ArrayBuffer",
outputType: "string",
args: []
},
"Scan for Embedded Files": {
module: "Default",
description: "Scans the data for potential embedded files by looking for magic bytes at all offsets. This operation is prone to false positives.<br><br>WARNING: Files over about 100KB in size will take a VERY long time to process.",
inputType: "byteArray",
inputType: "ArrayBuffer",
outputType: "string",
args: [
{
@@ -3740,7 +3676,7 @@ const OperationConfig = {
"<br><br>",
"EXIF data from photos usually contains information about the image file itself as well as the device used to create it.",
].join("\n"),
inputType: "byteArray",
inputType: "ArrayBuffer",
outputType: "string",
args: [],
},

View File

@@ -6,7 +6,6 @@ import CharEnc from "../../operations/CharEnc.js";
*
* Libraries:
* - cptable
* - CryptoJS
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017

View File

@@ -7,6 +7,7 @@ import Cipher from "../../operations/Cipher.js";
* Libraries:
* - CryptoJS
* - Blowfish
* - Forge
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
@@ -23,12 +24,12 @@ OpModules.Ciphers = {
"DES Decrypt": Cipher.runDesDec,
"Triple DES Encrypt": Cipher.runTripleDesEnc,
"Triple DES Decrypt": Cipher.runTripleDesDec,
"Rabbit Encrypt": Cipher.runRabbitEnc,
"Rabbit Decrypt": Cipher.runRabbitDec,
"Derive PBKDF2 key": Cipher.runPbkdf2,
"Derive EVP key": Cipher.runEvpkdf,
"RC4": Cipher.runRc4,
"RC4 Drop": Cipher.runRc4drop,
"RC2 Encrypt": Cipher.runRc2Enc,
"RC2 Decrypt": Cipher.runRc2Dec,
"Vigenère Encode": Cipher.runVigenereEnc,
"Vigenère Decode": Cipher.runVigenereDec,
"Bifid Cipher Encode": Cipher.runBifidEnc,
@@ -37,6 +38,7 @@ OpModules.Ciphers = {
"Affine Cipher Decode": Cipher.runAffineDec,
"Atbash Cipher": Cipher.runAtbash,
"Substitute": Cipher.runSubstitute,
"Pseudo-Random Number Generator": Cipher.runPRNG,
};
export default OpModules;

View File

@@ -38,7 +38,6 @@ import UUID from "../../operations/UUID.js";
*
* Libraries:
* - Utils.js
* - CryptoJS
* - otp
* - crypto
*

View File

@@ -134,11 +134,11 @@ const BCD = {
switch (outputFormat) {
case "Nibbles":
return nibbles.map(n => {
return Utils.padLeft(n.toString(2), 4);
return n.toString(2).padStart(4, "0");
}).join(" ");
case "Bytes":
return bytes.map(b => {
return Utils.padLeft(b.toString(2), 8);
return b.toString(2).padStart(8, "0");
}).join(" ");
case "Raw":
default:

View File

@@ -40,13 +40,13 @@ const Base64 = {
/**
* To Base64 operation.
*
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
runTo: function(input, args) {
const alphabet = args[0] || Base64.ALPHABET;
return Utils.toBase64(input, alphabet);
return Utils.toBase64(new Uint8Array(input), alphabet);
},

View File

@@ -67,7 +67,7 @@ const BitwiseOp = {
* @constant
* @default
*/
KEY_FORMAT: ["Hex", "Base64", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1"],
KEY_FORMAT: ["Hex", "Base64", "UTF8", "Latin1"],
/**
* XOR operation.
@@ -77,12 +77,10 @@ const BitwiseOp = {
* @returns {byteArray}
*/
runXor: function (input, args) {
let key = Utils.format[args[0].option].parse(args[0].string || ""),
const key = Utils.convertToByteArray(args[0].string || "", args[0].option),
scheme = args[1],
nullPreserving = args[2];
key = Utils.wordArrayToByteArray(key);
return BitwiseOp._bitOp(input, key, BitwiseOp._xor, nullPreserving, scheme);
},
@@ -200,8 +198,7 @@ const BitwiseOp = {
* @returns {byteArray}
*/
runAnd: function (input, args) {
let key = Utils.format[args[0].option].parse(args[0].string || "");
key = Utils.wordArrayToByteArray(key);
const key = Utils.convertToByteArray(args[0].string || "", args[0].option);
return BitwiseOp._bitOp(input, key, BitwiseOp._and);
},
@@ -215,8 +212,7 @@ const BitwiseOp = {
* @returns {byteArray}
*/
runOr: function (input, args) {
let key = Utils.format[args[0].option].parse(args[0].string || "");
key = Utils.wordArrayToByteArray(key);
const key = Utils.convertToByteArray(args[0].string || "", args[0].option);
return BitwiseOp._bitOp(input, key, BitwiseOp._or);
},
@@ -230,8 +226,7 @@ const BitwiseOp = {
* @returns {byteArray}
*/
runAdd: function (input, args) {
let key = Utils.format[args[0].option].parse(args[0].string || "");
key = Utils.wordArrayToByteArray(key);
const key = Utils.convertToByteArray(args[0].string || "", args[0].option);
return BitwiseOp._bitOp(input, key, BitwiseOp._add);
},
@@ -245,8 +240,7 @@ const BitwiseOp = {
* @returns {byteArray}
*/
runSub: function (input, args) {
let key = Utils.format[args[0].option].parse(args[0].string || "");
key = Utils.wordArrayToByteArray(key);
const key = Utils.convertToByteArray(args[0].string || "", args[0].option);
return BitwiseOp._bitOp(input, key, BitwiseOp._sub);
},

View File

@@ -31,13 +31,13 @@ const ByteRepr = {
/**
* To Hex operation.
*
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
runToHex: function(input, args) {
const delim = Utils.charRep[args[0] || "Space"];
return Utils.toHex(input, delim, 2);
return Utils.toHex(new Uint8Array(input), delim, 2);
},
@@ -266,7 +266,7 @@ const ByteRepr = {
padding = 8;
for (let i = 0; i < input.length; i++) {
output += Utils.pad(input[i].toString(2), padding) + delim;
output += input[i].toString(2).padStart(padding, "0") + delim;
}
if (delim.length) {

View File

@@ -1,5 +1,6 @@
import Utils from "../Utils.js";
import CryptoJS from "crypto-js";
import forge from "imports-loader?jQuery=>null!node-forge/dist/forge.min.js";
import {blowfish as Blowfish} from "sladex-blowfish";
@@ -18,132 +19,27 @@ const Cipher = {
* @constant
* @default
*/
IO_FORMAT1: ["Hex", "Base64", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1"],
IO_FORMAT1: ["Hex", "UTF8", "Latin1", "Base64"],
/**
* @constant
* @default
*/
IO_FORMAT2: ["UTF8", "Latin1", "Hex", "Base64"],
/**
* @constant
* @default
*/
IO_FORMAT3: ["Raw", "Hex"],
/**
* @constant
* @default
*/
IO_FORMAT4: ["Hex", "Raw"],
/**
* @constant
* @default
*/
IO_FORMAT2: ["UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1", "Hex", "Base64"],
/**
* @constant
* @default
*/
IO_FORMAT3: ["Hex", "Base64", "UTF16", "UTF16LE", "UTF16BE", "Latin1"],
/**
* @constant
* @default
*/
IO_FORMAT4: ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"],
/**
* @constant
* @default
*/
MODES: ["CBC", "CFB", "CTR", "OFB", "ECB"],
/**
* @constant
* @default
*/
PADDING: ["Pkcs7", "Iso97971", "AnsiX923", "Iso10126", "ZeroPadding", "NoPadding"],
/**
* @constant
* @default
*/
RESULT_TYPE: ["Show all", "Ciphertext", "Key", "IV", "Salt"],
/**
* Runs encryption operations using the CryptoJS framework.
*
* @private
* @param {function} algo - The CryptoJS algorithm to use
* @param {byteArray} input
* @param {function} args
* @returns {string}
*/
_enc: function (algo, input, args) {
let key = Utils.format[args[0].option].parse(args[0].string || ""),
iv = Utils.format[args[1].option].parse(args[1].string || ""),
salt = Utils.format[args[2].option].parse(args[2].string || ""),
mode = CryptoJS.mode[args[3]],
padding = CryptoJS.pad[args[4]],
resultOption = args[5].toLowerCase(),
outputFormat = args[6];
if (iv.sigBytes === 0) {
// Use passphrase rather than key. Need to convert it to a string.
key = key.toString(CryptoJS.enc.Latin1);
}
const encrypted = algo.encrypt(input, key, {
salt: salt.sigBytes > 0 ? salt : false,
iv: iv.sigBytes > 0 ? iv : null,
mode: mode,
padding: padding
});
let result = "";
if (resultOption === "show all") {
result += "Key: " + encrypted.key.toString(Utils.format[outputFormat]);
result += "\nIV: " + encrypted.iv.toString(Utils.format[outputFormat]);
if (encrypted.salt) result += "\nSalt: " + encrypted.salt.toString(Utils.format[outputFormat]);
result += "\n\nCiphertext: " + encrypted.ciphertext.toString(Utils.format[outputFormat]);
} else {
result = encrypted[resultOption].toString(Utils.format[outputFormat]);
}
return result;
},
/**
* Runs decryption operations using the CryptoJS framework.
*
* @private
* @param {function} algo - The CryptoJS algorithm to use
* @param {byteArray} input
* @param {function} args
* @returns {string}
*/
_dec: function (algo, input, args) {
let key = Utils.format[args[0].option].parse(args[0].string || ""),
iv = Utils.format[args[1].option].parse(args[1].string || ""),
salt = Utils.format[args[2].option].parse(args[2].string || ""),
mode = CryptoJS.mode[args[3]],
padding = CryptoJS.pad[args[4]],
inputFormat = args[5],
outputFormat = args[6];
// The ZeroPadding option causes a crash when the input length is 0
if (!input.length) {
return "No input";
}
const ciphertext = Utils.format[inputFormat].parse(input);
if (iv.sigBytes === 0) {
// Use passphrase rather than key. Need to convert it to a string.
key = key.toString(CryptoJS.enc.Latin1);
}
const decrypted = algo.decrypt({
ciphertext: ciphertext,
salt: salt.sigBytes > 0 ? salt : false
}, key, {
iv: iv.sigBytes > 0 ? iv : null,
mode: mode,
padding: padding
});
let result;
try {
result = decrypted.toString(Utils.format[outputFormat]);
} catch (err) {
result = "Decrypt error: " + err.message;
}
return result;
},
AES_MODES: ["CBC", "CFB", "OFB", "CTR", "GCM", "ECB"],
/**
* AES Encrypt operation.
@@ -153,7 +49,41 @@ const Cipher = {
* @returns {string}
*/
runAesEnc: function (input, args) {
return Cipher._enc(CryptoJS.AES, input, args);
const key = Utils.convertToByteArray(args[0].string, args[0].option),
iv = Utils.convertToByteArray(args[1].string, args[1].option),
mode = args[2],
inputType = args[3],
outputType = args[4];
if ([16, 24, 32].indexOf(key.length) < 0) {
return `Invalid key length: ${key.length} bytes
The following algorithms will be used based on the size of the key:
16 bytes = AES-128
24 bytes = AES-192
32 bytes = AES-256`;
}
input = Utils.convertToByteString(input, inputType);
const cipher = forge.cipher.createCipher("AES-" + mode, key);
cipher.start({iv: iv});
cipher.update(forge.util.createBuffer(input));
cipher.finish();
if (outputType === "Hex") {
if (mode === "GCM") {
return cipher.output.toHex() + "\n\n" +
"Tag: " + cipher.mode.tag.toHex();
}
return cipher.output.toHex();
} else {
if (mode === "GCM") {
return cipher.output.getBytes() + "\n\n" +
"Tag: " + cipher.mode.tag.getBytes();
}
return cipher.output.getBytes();
}
},
@@ -165,10 +95,46 @@ const Cipher = {
* @returns {string}
*/
runAesDec: function (input, args) {
return Cipher._dec(CryptoJS.AES, input, args);
const key = Utils.convertToByteArray(args[0].string, args[0].option),
iv = Utils.convertToByteArray(args[1].string, args[1].option),
mode = args[2],
inputType = args[3],
outputType = args[4],
gcmTag = Utils.convertToByteString(args[5].string, args[5].option);
if ([16, 24, 32].indexOf(key.length) < 0) {
return `Invalid key length: ${key.length} bytes
The following algorithms will be used based on the size of the key:
16 bytes = AES-128
24 bytes = AES-192
32 bytes = AES-256`;
}
input = Utils.convertToByteString(input, inputType);
const decipher = forge.cipher.createDecipher("AES-" + mode, key);
decipher.start({
iv: iv,
tag: gcmTag
});
decipher.update(forge.util.createBuffer(input));
const result = decipher.finish();
if (result) {
return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes();
} else {
return "Unable to decrypt input with these parameters.";
}
},
/**
* @constant
* @default
*/
DES_MODES: ["CBC", "CFB", "OFB", "CTR", "ECB"],
/**
* DES Encrypt operation.
*
@@ -177,7 +143,27 @@ const Cipher = {
* @returns {string}
*/
runDesEnc: function (input, args) {
return Cipher._enc(CryptoJS.DES, input, args);
const key = Utils.convertToByteString(args[0].string, args[0].option),
iv = Utils.convertToByteArray(args[1].string, args[1].option),
mode = args[2],
inputType = args[3],
outputType = args[4];
if (key.length !== 8) {
return `Invalid key length: ${key.length} bytes
DES uses a key length of 8 bytes (64 bits).
Triple DES uses a key length of 24 bytes (192 bits).`;
}
input = Utils.convertToByteString(input, inputType);
const cipher = forge.cipher.createCipher("DES-" + mode, key);
cipher.start({iv: iv});
cipher.update(forge.util.createBuffer(input));
cipher.finish();
return outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes();
},
@@ -189,7 +175,31 @@ const Cipher = {
* @returns {string}
*/
runDesDec: function (input, args) {
return Cipher._dec(CryptoJS.DES, input, args);
const key = Utils.convertToByteString(args[0].string, args[0].option),
iv = Utils.convertToByteArray(args[1].string, args[1].option),
mode = args[2],
inputType = args[3],
outputType = args[4];
if (key.length !== 8) {
return `Invalid key length: ${key.length} bytes
DES uses a key length of 8 bytes (64 bits).
Triple DES uses a key length of 24 bytes (192 bits).`;
}
input = Utils.convertToByteString(input, inputType);
const decipher = forge.cipher.createDecipher("DES-" + mode, key);
decipher.start({iv: iv});
decipher.update(forge.util.createBuffer(input));
const result = decipher.finish();
if (result) {
return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes();
} else {
return "Unable to decrypt input with these parameters.";
}
},
@@ -201,7 +211,27 @@ const Cipher = {
* @returns {string}
*/
runTripleDesEnc: function (input, args) {
return Cipher._enc(CryptoJS.TripleDES, input, args);
const key = Utils.convertToByteString(args[0].string, args[0].option),
iv = Utils.convertToByteArray(args[1].string, args[1].option),
mode = args[2],
inputType = args[3],
outputType = args[4];
if (key.length !== 24) {
return `Invalid key length: ${key.length} bytes
Triple DES uses a key length of 24 bytes (192 bits).
DES uses a key length of 8 bytes (64 bits).`;
}
input = Utils.convertToByteString(input, inputType);
const cipher = forge.cipher.createCipher("3DES-" + mode, key);
cipher.start({iv: iv});
cipher.update(forge.util.createBuffer(input));
cipher.finish();
return outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes();
},
@@ -213,31 +243,79 @@ const Cipher = {
* @returns {string}
*/
runTripleDesDec: function (input, args) {
return Cipher._dec(CryptoJS.TripleDES, input, args);
const key = Utils.convertToByteString(args[0].string, args[0].option),
iv = Utils.convertToByteArray(args[1].string, args[1].option),
mode = args[2],
inputType = args[3],
outputType = args[4];
if (key.length !== 24) {
return `Invalid key length: ${key.length} bytes
Triple DES uses a key length of 24 bytes (192 bits).
DES uses a key length of 8 bytes (64 bits).`;
}
input = Utils.convertToByteString(input, inputType);
const decipher = forge.cipher.createDecipher("3DES-" + mode, key);
decipher.start({iv: iv});
decipher.update(forge.util.createBuffer(input));
const result = decipher.finish();
if (result) {
return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes();
} else {
return "Unable to decrypt input with these parameters.";
}
},
/**
* Rabbit Encrypt operation.
* RC2 Encrypt operation.
*
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
runRabbitEnc: function (input, args) {
return Cipher._enc(CryptoJS.Rabbit, input, args);
runRc2Enc: function (input, args) {
const key = Utils.convertToByteString(args[0].string, args[0].option),
iv = Utils.convertToByteString(args[1].string, args[1].option),
inputType = args[2],
outputType = args[3],
cipher = forge.rc2.createEncryptionCipher(key);
input = Utils.convertToByteString(input, inputType);
cipher.start(iv || null);
cipher.update(forge.util.createBuffer(input));
cipher.finish();
return outputType === "Hex" ? cipher.output.toHex() : cipher.output.getBytes();
},
/**
* Rabbit Decrypt operation.
* RC2 Decrypt operation.
*
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
runRabbitDec: function (input, args) {
return Cipher._dec(CryptoJS.Rabbit, input, args);
runRc2Dec: function (input, args) {
const key = Utils.convertToByteString(args[0].string, args[0].option),
iv = Utils.convertToByteString(args[1].string, args[1].option),
inputType = args[2],
outputType = args[3],
decipher = forge.rc2.createDecryptionCipher(key);
input = Utils.convertToByteString(input, inputType);
decipher.start(iv || null);
decipher.update(forge.util.createBuffer(input));
decipher.finish();
return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes();
},
@@ -245,12 +323,29 @@ const Cipher = {
* @constant
* @default
*/
BLOWFISH_MODES: ["ECB", "CBC", "PCBC", "CFB", "OFB", "CTR"],
BLOWFISH_MODES: ["CBC", "PCBC", "CFB", "OFB", "CTR", "ECB"],
/**
* @constant
* @default
*/
BLOWFISH_OUTPUT_TYPES: ["Base64", "Hex", "String", "Raw"],
BLOWFISH_OUTPUT_TYPES: ["Hex", "Base64", "Raw"],
/**
* Lookup table for Blowfish output types.
*
* @private
*/
_BLOWFISH_OUTPUT_TYPE_LOOKUP: {
Base64: 0, Hex: 1, String: 2, Raw: 3
},
/**
* Lookup table for Blowfish modes.
*
* @private
*/
_BLOWFISH_MODE_LOOKUP: {
ECB: 0, CBC: 1, PCBC: 2, CFB: 3, OFB: 4, CTR: 5
},
/**
* Blowfish Encrypt operation.
@@ -260,19 +355,24 @@ const Cipher = {
* @returns {string}
*/
runBlowfishEnc: function (input, args) {
let key = Utils.format[args[0].option].parse(args[0].string).toString(Utils.format.Latin1),
mode = args[1],
outputFormat = args[2];
const key = Utils.convertToByteString(args[0].string, args[0].option),
iv = Utils.convertToByteArray(args[1].string, args[1].option),
mode = args[2],
inputType = args[3],
outputType = args[4];
if (key.length === 0) return "Enter a key";
let encHex = Blowfish.encrypt(input, key, {
outputType: 1,
cipherMode: Cipher.BLOWFISH_MODES.indexOf(mode)
}),
enc = CryptoJS.enc.Hex.parse(encHex);
input = Utils.convertToByteString(input, inputType);
return enc.toString(Utils.format[outputFormat]);
Blowfish.setIV(Utils.toBase64(iv), 0);
const enc = Blowfish.encrypt(input, key, {
outputType: Cipher._BLOWFISH_OUTPUT_TYPE_LOOKUP[outputType],
cipherMode: Cipher._BLOWFISH_MODE_LOOKUP[mode]
});
return outputType === "Raw" ? Utils.byteArrayToChars(enc) : enc ;
},
@@ -284,18 +384,24 @@ const Cipher = {
* @returns {string}
*/
runBlowfishDec: function (input, args) {
let key = Utils.format[args[0].option].parse(args[0].string).toString(Utils.format.Latin1),
mode = args[1],
inputFormat = args[2];
const key = Utils.convertToByteString(args[0].string, args[0].option),
iv = Utils.convertToByteArray(args[1].string, args[1].option),
mode = args[2],
inputType = args[3],
outputType = args[4];
if (key.length === 0) return "Enter a key";
input = Utils.format[inputFormat].parse(input);
input = inputType === "Raw" ? Utils.strToByteArray(input) : input;
return Blowfish.decrypt(input.toString(CryptoJS.enc.Base64), key, {
outputType: 0, // This actually means inputType. The library is weird.
cipherMode: Cipher.BLOWFISH_MODES.indexOf(mode)
Blowfish.setIV(Utils.toBase64(iv), 0);
const result = Blowfish.decrypt(input, key, {
outputType: Cipher._BLOWFISH_OUTPUT_TYPE_LOOKUP[inputType], // This actually means inputType. The library is weird.
cipherMode: Cipher._BLOWFISH_MODE_LOOKUP[mode]
});
return outputType === "Hex" ? Utils.toHexFast(Utils.strToByteArray(result)) : result;
},
@@ -303,7 +409,7 @@ const Cipher = {
* @constant
* @default
*/
KDF_KEY_SIZE: 256,
KDF_KEY_SIZE: 128,
/**
* @constant
* @default
@@ -313,7 +419,7 @@ const Cipher = {
* @constant
* @default
*/
HASHERS: ["MD5", "SHA1", "SHA224", "SHA256", "SHA384", "SHA512", "SHA3", "RIPEMD160"],
HASHERS: ["SHA1", "SHA256", "SHA384", "SHA512", "MD5"],
/**
* Derive PBKDF2 key operation.
@@ -323,20 +429,15 @@ const Cipher = {
* @returns {string}
*/
runPbkdf2: function (input, args) {
let keySize = args[0] / 32,
iterations = args[1],
hasher = args[2],
salt = CryptoJS.enc.Hex.parse(args[3] || ""),
inputFormat = args[4],
outputFormat = args[5],
passphrase = Utils.format[inputFormat].parse(input),
key = CryptoJS.PBKDF2(passphrase, salt, {
keySize: keySize,
hasher: CryptoJS.algo[hasher],
iterations: iterations,
});
const passphrase = Utils.convertToByteString(args[0].string, args[0].option),
keySize = args[1],
iterations = args[2],
hasher = args[3],
salt = Utils.convertToByteString(args[4].string, args[4].option) ||
forge.random.getBytesSync(keySize),
derivedKey = forge.pkcs5.pbkdf2(passphrase, salt, iterations, keySize / 8, hasher.toLowerCase());
return key.toString(Utils.format[outputFormat]);
return forge.util.bytesToHex(derivedKey);
},
@@ -348,23 +449,33 @@ const Cipher = {
* @returns {string}
*/
runEvpkdf: function (input, args) {
let keySize = args[0] / 32,
iterations = args[1],
hasher = args[2],
salt = CryptoJS.enc.Hex.parse(args[3] || ""),
inputFormat = args[4],
outputFormat = args[5],
passphrase = Utils.format[inputFormat].parse(input),
const passphrase = 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, {
keySize: keySize,
hasher: CryptoJS.algo[hasher],
iterations: iterations,
});
return key.toString(Utils.format[outputFormat]);
return key.toString(CryptoJS.enc.Hex);
},
/**
* @constant
* @default
*/
RC4_KEY_FORMAT: ["UTF8", "UTF16", "UTF16LE", "UTF16BE", "Latin1", "Hex", "Base64"],
/**
* @constant
* @default
*/
CJS_IO_FORMAT: ["Latin1", "UTF8", "UTF16", "UTF16LE", "UTF16BE", "Hex", "Base64"],
/**
* RC4 operation.
*
@@ -373,11 +484,11 @@ const Cipher = {
* @returns {string}
*/
runRc4: function (input, args) {
let message = Utils.format[args[1]].parse(input),
passphrase = Utils.format[args[0].option].parse(args[0].string),
let message = Cipher._format[args[1]].parse(input),
passphrase = Cipher._format[args[0].option].parse(args[0].string),
encrypted = CryptoJS.RC4.encrypt(message, passphrase);
return encrypted.ciphertext.toString(Utils.format[args[2]]);
return encrypted.ciphertext.toString(Cipher._format[args[2]]);
},
@@ -395,12 +506,59 @@ const Cipher = {
* @returns {string}
*/
runRc4drop: function (input, args) {
let message = Utils.format[args[1]].parse(input),
passphrase = Utils.format[args[0].option].parse(args[0].string),
let message = Cipher._format[args[1]].parse(input),
passphrase = Cipher._format[args[0].option].parse(args[0].string),
drop = args[3],
encrypted = CryptoJS.RC4Drop.encrypt(message, passphrase, { drop: drop });
return encrypted.ciphertext.toString(Utils.format[args[2]]);
return encrypted.ciphertext.toString(Cipher._format[args[2]]);
},
/**
* @constant
* @default
*/
PRNG_BYTES: 32,
PRNG_OUTPUT: ["Hex", "Number", "Byte array", "Raw"],
/**
* Pseudo-Random Number Generator operation.
*
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
runPRNG: function(input, args) {
const numBytes = args[0],
outputAs = args[1];
let bytes;
if (ENVIRONMENT_IS_WORKER() && self.crypto) {
bytes = self.crypto.getRandomValues(new Uint8Array(numBytes));
bytes = Utils.arrayBufferToStr(bytes.buffer);
} else {
bytes = forge.random.getBytesSync(numBytes);
}
let value = 0,
i;
switch (outputAs) {
case "Hex":
return forge.util.bytesToHex(bytes);
case "Number":
for (i = bytes.length - 1; i >= 0; i--) {
value = (value * 256) + bytes.charCodeAt(i);
}
return value.toString();
case "Byte array":
return JSON.stringify(Utils.strToCharcode(bytes));
case "Raw":
default:
return bytes;
}
},
@@ -783,6 +941,23 @@ const Cipher = {
return output;
},
/**
* A mapping of string formats to their classes in the CryptoJS library.
*
* @private
* @constant
*/
_format: {
"Hex": CryptoJS.enc.Hex,
"Base64": CryptoJS.enc.Base64,
"UTF8": CryptoJS.enc.Utf8,
"UTF16": CryptoJS.enc.Utf16,
"UTF16LE": CryptoJS.enc.Utf16LE,
"UTF16BE": CryptoJS.enc.Utf16BE,
"Latin1": CryptoJS.enc.Latin1,
},
};
export default Cipher;
@@ -827,3 +1002,27 @@ CryptoJS.kdf.OpenSSL.execute = function (password, keySize, ivSize, salt) {
// Return params
return CryptoJS.lib.CipherParams.create({ key: key, iv: iv, salt: salt });
};
/**
* Override for the CryptoJS Hex encoding parser to remove whitespace before attempting to parse
* the hex string.
*
* @param {string} hexStr
* @returns {CryptoJS.lib.WordArray}
*/
CryptoJS.enc.Hex.parse = function (hexStr) {
// Remove whitespace
hexStr = hexStr.replace(/\s/g, "");
// Shortcut
const hexStrLength = hexStr.length;
// Convert
const words = [];
for (let i = 0; i < hexStrLength; i += 2) {
words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4);
}
return new CryptoJS.lib.WordArray.init(words, hexStrLength / 2);
};

View File

@@ -418,9 +418,9 @@ const Compress = {
}
};
const fileSize = Utils.padLeft(input.length.toString(8), 11, "0");
const fileSize = input.length.toString(8).padStart(11, "0");
const currentUnixTimestamp = Math.floor(Date.now() / 1000);
const lastModTime = Utils.padLeft(currentUnixTimestamp.toString(8), 11, "0");
const lastModTime = currentUnixTimestamp.toString(8).padStart(11, "0");
const file = {
fileName: Utils.padBytesRight(args[0], 100),
@@ -452,7 +452,7 @@ const Compress = {
}
});
}
checksum = Utils.padBytesRight(Utils.padLeft(checksum.toString(8), 7, "0"), 8);
checksum = Utils.padBytesRight(checksum.toString(8).padStart(7, "0"), 8);
file.checksum = checksum;
const tarball = new Tarball();

View File

@@ -81,22 +81,23 @@ const Entropy = {
/**
* Frequency distribution operation.
*
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {html}
*/
runFreqDistrib: function (input, args) {
if (!input.length) return "No data";
const data = new Uint8Array(input);
if (!data.length) return "No data";
let distrib = new Array(256).fill(0),
percentages = new Array(256),
len = input.length,
len = data.length,
showZeroes = args[0],
i;
// Count bytes
for (i = 0; i < len; i++) {
distrib[input[i]]++;
distrib[data[i]]++;
}
// Calculate percentages
@@ -126,7 +127,7 @@ const Entropy = {
for (i = 0; i < 256; i++) {
if (distrib[i] || showZeroes) {
output += " " + Utils.hex(i, 2) + " (" +
Utils.padRight(percentages[i].toFixed(2).replace(".00", "") + "%)", 8) +
(percentages[i].toFixed(2).replace(".00", "") + "%)").padEnd(8, " ") +
Array(Math.ceil(percentages[i])+1).join("|") + "\n";
}
}
@@ -138,21 +139,22 @@ const Entropy = {
/**
* Chi Square operation.
*
* @param {byteArray} data
* @param {ArrayBuffer} data
* @param {Object[]} args
* @returns {number}
*/
runChiSq: function(input, args) {
const data = new Uint8Array(input);
let distArray = new Array(256).fill(0),
total = 0;
for (let i = 0; i < input.length; i++) {
distArray[input[i]]++;
for (let i = 0; i < data.length; i++) {
distArray[data[i]]++;
}
for (let i = 0; i < distArray.length; i++) {
if (distArray[i] > 0) {
total += Math.pow(distArray[i] - input.length / 256, 2) / (input.length / 256);
total += Math.pow(distArray[i] - data.length / 256, 2) / (data.length / 256);
}
}

View File

@@ -15,12 +15,13 @@ const FileType = {
/**
* Detect File Type operation.
*
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
runDetect: function(input, args) {
const type = FileType.magicType(input);
const data = new Uint8Array(input),
type = FileType.magicType(data);
if (!type) {
return "Unknown file type. Have you tried checking the entropy of this data to determine whether it might be encrypted or compressed?";
@@ -46,20 +47,21 @@ const FileType = {
/**
* Scan for Embedded Files operation.
*
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
runScanForEmbeddedFiles: function(input, args) {
let output = "Scanning data for 'magic bytes' which may indicate embedded files. The following results may be false positives and should not be treat as reliable. Any suffiently long file is likely to contain these magic bytes coincidentally.\n",
type,
ignoreCommon = args[0],
commonExts = ["ico", "ttf", ""],
numFound = 0,
numCommonFound = 0;
const ignoreCommon = args[0],
commonExts = ["ico", "ttf", ""],
data = new Uint8Array(input);
for (let i = 0; i < input.length; i++) {
type = FileType.magicType(input.slice(i));
for (let i = 0; i < data.length; i++) {
type = FileType.magicType(data.slice(i));
if (type) {
if (ignoreCommon && commonExts.indexOf(type.ext) > -1) {
numCommonFound++;
@@ -96,7 +98,7 @@ const FileType = {
* Given a buffer, detects magic byte sequences at specific positions and returns the
* extension and mime type.
*
* @param {byteArray} buf
* @param {Uint8Array} buf
* @returns {Object} type
* @returns {string} type.ext - File extension
* @returns {string} type.mime - Mime type

View File

@@ -215,9 +215,9 @@ const HTML = {
k = k.toFixed(2);
let hex = "#" +
Utils.padLeft(Math.round(r).toString(16), 2) +
Utils.padLeft(Math.round(g).toString(16), 2) +
Utils.padLeft(Math.round(b).toString(16), 2),
Math.round(r).toString(16).padStart(2, "0") +
Math.round(g).toString(16).padStart(2, "0") +
Math.round(b).toString(16).padStart(2, "0"),
rgb = "rgb(" + r + ", " + g + ", " + b + ")",
rgba = "rgba(" + r + ", " + g + ", " + b + ", " + a + ")",
hsl = "hsl(" + h + ", " + s + "%, " + l + "%)",

View File

@@ -31,18 +31,19 @@ const Hexdump = {
/**
* To Hexdump operation.
*
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
runTo: function(input, args) {
const data = new Uint8Array(input);
const length = args[0] || Hexdump.WIDTH;
const upperCase = args[1];
const includeFinalLength = args[2];
let output = "", padding = 2;
for (let i = 0; i < input.length; i += length) {
const buff = input.slice(i, i+length);
for (let i = 0; i < data.length; i += length) {
const buff = data.slice(i, i+length);
let hexa = "";
for (let j = 0; j < buff.length; j++) {
hexa += Utils.hex(buff[j], padding) + " ";
@@ -56,10 +57,10 @@ const Hexdump = {
}
output += lineNo + " " +
Utils.padRight(hexa, (length*(padding+1))) +
" |" + Utils.padRight(Utils.printable(Utils.byteArrayToChars(buff)), buff.length) + "|\n";
hexa.padEnd(length*(padding+1), " ") +
" |" + Utils.printable(Utils.byteArrayToChars(buff)).padEnd(buff.length, " ") + "|\n";
if (includeFinalLength && i+buff.length === input.length) {
if (includeFinalLength && i+buff.length === data.length) {
output += Utils.hex(i+buff.length, 8) + "\n";
}
}

View File

@@ -20,14 +20,13 @@ const Image = {
*
* Extracts EXIF data from a byteArray, representing a JPG or a TIFF image.
*
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
runExtractEXIF(input, args) {
try {
const bytes = Uint8Array.from(input);
const parser = ExifParser.create(bytes.buffer);
const parser = ExifParser.create(input);
const result = parser.parse();
let lines = [];
@@ -53,7 +52,7 @@ const Image = {
* @author David Moodie [davidmoodie12@gmail.com]
* @param {byteArray} input
* @param {Object[]} args
* @returns {string}
* @returns {byteArray}
*/
runRemoveEXIF(input, args) {
// Do nothing if input is empty

View File

@@ -121,8 +121,7 @@ const PublicKey = {
// Format Public Key fields
for (let i = 0; i < pkFields.length; i++) {
pkStr += " " + pkFields[i].key + ":" +
Utils.padLeft(
pkFields[i].value + "\n",
(pkFields[i].value + "\n").padStart(
18 - (pkFields[i].key.length + 3) + pkFields[i].value.length + 1,
" "
);
@@ -286,9 +285,9 @@ ${extensions}`;
key = fields[i].split("=")[0];
value = fields[i].split("=")[1];
str = Utils.padRight(key, maxKeyLen) + " = " + value + "\n";
str = key.padEnd(maxKeyLen, " ") + " = " + value + "\n";
output += Utils.padLeft(str, indent + str.length, " ");
output += str.padStart(indent + str.length, " ");
}
return output.slice(0, -1);
@@ -314,7 +313,7 @@ ${extensions}`;
if (i === 0) {
output += str;
} else {
output += Utils.padLeft(str, indent + str.length, " ");
output += str.padStart(indent + str.length, " ");
}
}

View File

@@ -158,7 +158,7 @@ const SeqUtils = {
width = lines.length.toString().length;
for (let n = 0; n < lines.length; n++) {
output += Utils.pad((n+1).toString(), width, " ") + " " + lines[n] + "\n";
output += (n+1).toString().padStart(width, " ") + " " + lines[n] + "\n";
}
return output.slice(0, output.length-1);
},

View File

@@ -1,6 +1,3 @@
import Utils from "../Utils.js";
/**
* Tidy operations.
*
@@ -104,32 +101,39 @@ const Tidy = {
/**
* Drop bytes operation.
*
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
* @returns {ArrayBuffer}
*/
runDropBytes: function(input, args) {
let start = args[0],
const start = args[0],
length = args[1],
applyToEachLine = args[2];
if (start < 0 || length < 0)
throw "Error: Invalid value";
if (!applyToEachLine)
return input.slice(0, start).concat(input.slice(start+length, input.length));
if (!applyToEachLine) {
const left = input.slice(0, start),
right = input.slice(start + length, input.byteLength);
let result = new Uint8Array(left.byteLength + right.byteLength);
result.set(new Uint8Array(left), 0);
result.set(new Uint8Array(right), left.byteLength);
return result.buffer;
}
// Split input into lines
const data = new Uint8Array(input);
let lines = [],
line = [],
i;
for (i = 0; i < input.length; i++) {
if (input[i] === 0x0a) {
for (i = 0; i < data.length; i++) {
if (data[i] === 0x0a) {
lines.push(line);
line = [];
} else {
line.push(input[i]);
line.push(data[i]);
}
}
lines.push(line);
@@ -139,7 +143,7 @@ const Tidy = {
output = output.concat(lines[i].slice(0, start).concat(lines[i].slice(start+length, lines[i].length)));
output.push(0x0a);
}
return output.slice(0, output.length-1);
return new Uint8Array(output.slice(0, output.length-1)).buffer;
},
@@ -157,12 +161,12 @@ const Tidy = {
/**
* Take bytes operation.
*
* @param {byteArray} input
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
* @returns {ArrayBuffer}
*/
runTakeBytes: function(input, args) {
let start = args[0],
const start = args[0],
length = args[1],
applyToEachLine = args[2];
@@ -173,16 +177,17 @@ const Tidy = {
return input.slice(start, start+length);
// Split input into lines
const data = new Uint8Array(input);
let lines = [],
line = [];
let i;
line = [],
i;
for (i = 0; i < input.length; i++) {
if (input[i] === 0x0a) {
for (i = 0; i < data.length; i++) {
if (data[i] === 0x0a) {
lines.push(line);
line = [];
} else {
line.push(input[i]);
line.push(data[i]);
}
}
lines.push(line);
@@ -192,7 +197,7 @@ const Tidy = {
output = output.concat(lines[i].slice(start, start+length));
output.push(0x0a);
}
return output.slice(0, output.length-1);
return new Uint8Array(output.slice(0, output.length-1)).buffer;
},
@@ -229,11 +234,11 @@ const Tidy = {
if (position === "Start") {
for (i = 0; i < lines.length; i++) {
output += Utils.padLeft(lines[i], lines[i].length+len, chr) + "\n";
output += lines[i].padStart(lines[i].length+len, chr) + "\n";
}
} else if (position === "End") {
for (i = 0; i < lines.length; i++) {
output += Utils.padRight(lines[i], lines[i].length+len, chr) + "\n";
output += lines[i].padEnd(lines[i].length+len, chr) + "\n";
}
}

View File

@@ -1,5 +1,4 @@
/* globals unescape */
import Utils from "../Utils.js";
import url from "url";
@@ -78,7 +77,7 @@ const URL_ = {
output += "Arguments:\n";
for (let key in uri.query) {
output += "\t" + Utils.padRight(key, padding);
output += "\t" + key.padEnd(padding, " ");
if (uri.query[key].length) {
output += " = " + uri.query[key] + "\n";
} else {

View File

@@ -49,9 +49,11 @@ App.prototype.setup = function() {
this.manager.setup();
this.resetLayout();
this.setCompileMessage();
this.loadURIParams();
log.debug("App loaded");
this.appLoaded = true;
this.loadURIParams();
this.loaded();
};
@@ -91,7 +93,7 @@ App.prototype.loaded = function() {
* @param {boolean} [logToConsole=false]
*/
App.prototype.handleError = function(err, logToConsole) {
if (logToConsole) console.error(err);
if (logToConsole) log.error(err);
const msg = err.displayStr || err.toString();
this.alert(msg, "danger", this.options.errorTimeout, !this.options.showErrors);
};
@@ -129,6 +131,7 @@ App.prototype.autoBake = function() {
if (this.autoBakePause) return false;
if (this.autoBake_ && !this.baking) {
log.debug("Auto-baking");
this.bake();
} else {
this.manager.controls.showStaleIndicator();
@@ -569,7 +572,7 @@ App.prototype.isLocalStorageAvailable = function() {
App.prototype.alert = function(str, style, timeout, silent) {
const time = new Date();
console.log("[" + time.toLocaleString() + "] " + str);
log.info("[" + time.toLocaleString() + "] " + str);
if (silent) return;
style = style || "danger";

View File

@@ -1,6 +1,3 @@
import Utils from "../core/Utils.js";
/**
* Waiter to handle events related to highlighting in CyberChef.
*
@@ -312,9 +309,9 @@ HighlighterWaiter.prototype.outputHtmlMousemove = function(e) {
HighlighterWaiter.prototype.selectionInfo = function(start, end) {
const len = end.toString().length;
const width = len < 2 ? 2 : len;
const startStr = Utils.pad(start.toString(), width, " ").replace(/ /g, "&nbsp;");
const endStr = Utils.pad(end.toString(), width, " ").replace(/ /g, "&nbsp;");
const lenStr = Utils.pad((end-start).toString(), width, " ").replace(/ /g, "&nbsp;");
const startStr = start.toString().padStart(width, " ").replace(/ /g, "&nbsp;");
const endStr = end.toString().padStart(width, " ").replace(/ /g, "&nbsp;");
const lenStr = (end-start).toString().padStart(width, " ").replace(/ /g, "&nbsp;");
return "start: " + startStr + "<br>end: " + endStr + "<br>length: " + lenStr;
};
@@ -402,7 +399,7 @@ HighlighterWaiter.prototype.highlight = function(textarea, highlighter, pos) {
// Check if there is a carriage return in the output dish as this will not
// be displayed by the HTML textarea and will mess up highlighting offsets.
if (!this.app.dishStr || this.app.dishStr.indexOf("\r") >= 0) return false;
if (this.manager.output.containsCR()) return false;
const startPlaceholder = "[startHighlight]";
const startPlaceholderRegex = /\[startHighlight\]/g;

View File

@@ -1,4 +1,4 @@
import Utils from "../core/Utils.js";
import LoaderWorker from "worker-loader?inline&fallback=false!./LoaderWorker.js";
/**
@@ -33,6 +33,9 @@ const InputWaiter = function(app, manager) {
144, //Num
145, //Scroll
];
this.loaderWorker = null;
this.fileBuffer = null;
};
@@ -42,20 +45,47 @@ const InputWaiter = function(app, manager) {
* @returns {string}
*/
InputWaiter.prototype.get = function() {
return document.getElementById("input-text").value;
return this.fileBuffer || document.getElementById("input-text").value;
};
/**
* Sets the input in the input textarea.
* Sets the input in the input area.
*
* @param {string} input
* @param {string|File} input
*
* @fires Manager#statechange
*/
InputWaiter.prototype.set = function(input) {
document.getElementById("input-text").value = input;
window.dispatchEvent(this.manager.statechange);
const inputText = document.getElementById("input-text");
if (input instanceof File) {
this.setFile(input);
inputText.value = "";
} else {
inputText.value = input;
window.dispatchEvent(this.manager.statechange);
}
};
/**
* Shows file details.
*
* @param {File} file
*/
InputWaiter.prototype.setFile = function(file) {
// Display file overlay in input area with details
const fileOverlay = document.getElementById("input-file"),
fileName = document.getElementById("input-file-name"),
fileSize = document.getElementById("input-file-size"),
fileType = document.getElementById("input-file-type"),
fileLoaded = document.getElementById("input-file-loaded");
fileOverlay.style.display = "block";
fileName.textContent = file.name;
fileSize.textContent = file.size.toLocaleString() + " bytes";
fileType.textContent = file.type || "unknown";
fileLoaded.textContent = "0%";
};
@@ -69,8 +99,8 @@ InputWaiter.prototype.setInputInfo = function(length, lines) {
let width = length.toString().length;
width = width < 2 ? 2 : width;
const lengthStr = Utils.pad(length.toString(), width, " ").replace(/ /g, "&nbsp;");
const linesStr = Utils.pad(lines.toString(), width, " ").replace(/ /g, "&nbsp;");
const lengthStr = length.toString().padStart(width, " ").replace(/ /g, "&nbsp;");
const linesStr = lines.toString().padStart(width, " ").replace(/ /g, "&nbsp;");
document.getElementById("input-info").innerHTML = "length: " + lengthStr + "<br>lines: " + linesStr;
};
@@ -118,7 +148,7 @@ InputWaiter.prototype.inputDragover = function(e) {
e.stopPropagation();
e.preventDefault();
e.target.classList.add("dropping-file");
e.target.closest("#input-text,#input-file").classList.add("dropping-file");
};
@@ -131,7 +161,8 @@ InputWaiter.prototype.inputDragover = function(e) {
InputWaiter.prototype.inputDragleave = function(e) {
e.stopPropagation();
e.preventDefault();
e.target.classList.remove("dropping-file");
document.getElementById("input-text").classList.remove("dropping-file");
document.getElementById("input-file").classList.remove("dropping-file");
};
@@ -149,55 +180,62 @@ InputWaiter.prototype.inputDrop = function(e) {
e.stopPropagation();
e.preventDefault();
const el = e.target;
const file = e.dataTransfer.files[0];
const text = e.dataTransfer.getData("Text");
const reader = new FileReader();
let inputCharcode = "";
let offset = 0;
const CHUNK_SIZE = 20480; // 20KB
const setInput = function() {
const recipeConfig = this.app.getRecipeConfig();
if (!recipeConfig[0] || recipeConfig[0].op !== "From Hex") {
recipeConfig.unshift({op: "From Hex", args: ["Space"]});
this.app.setRecipeConfig(recipeConfig);
}
document.getElementById("input-text").classList.remove("dropping-file");
document.getElementById("input-file").classList.remove("dropping-file");
this.set(inputCharcode);
el.classList.remove("loadingFile");
}.bind(this);
const seek = function() {
if (offset >= file.size) {
setInput();
return;
}
el.value = "Processing... " + Math.round(offset / file.size * 100) + "%";
const slice = file.slice(offset, offset + CHUNK_SIZE);
reader.readAsArrayBuffer(slice);
};
reader.onload = function(e) {
const data = new Uint8Array(reader.result);
inputCharcode += Utils.toHexFast(data);
offset += CHUNK_SIZE;
seek();
};
el.classList.remove("dropping-file");
if (text) {
this.closeFile();
this.set(text);
return;
}
if (file) {
el.classList.add("loadingFile");
seek();
} else if (text) {
this.set(text);
this.closeFile();
this.loaderWorker = new LoaderWorker();
this.loaderWorker.addEventListener("message", this.handleLoaderMessage.bind(this));
this.loaderWorker.postMessage({"file": file});
this.set(file);
}
};
/**
* Handler for messages sent back by the LoaderWorker.
*
* @param {MessageEvent} e
*/
InputWaiter.prototype.handleLoaderMessage = function(e) {
const r = e.data;
if (r.hasOwnProperty("progress")) {
const fileLoaded = document.getElementById("input-file-loaded");
fileLoaded.textContent = r.progress + "%";
}
if (r.hasOwnProperty("error")) {
this.app.alert(r.error, "danger", 10000);
}
if (r.hasOwnProperty("fileBuffer")) {
log.debug("Input file loaded");
this.fileBuffer = r.fileBuffer;
window.dispatchEvent(this.manager.statechange);
}
};
/**
* Handler for file close events.
*/
InputWaiter.prototype.closeFile = function() {
if (this.loaderWorker) this.loaderWorker.terminate();
this.fileBuffer = null;
document.getElementById("input-file").style.display = "none";
};
/**
* Handler for clear IO events.
* Resets the input, output and info areas.
@@ -205,6 +243,8 @@ InputWaiter.prototype.inputDrop = function(e) {
* @fires Manager#statechange
*/
InputWaiter.prototype.clearIoClick = function() {
this.closeFile();
this.manager.output.closeFile();
this.manager.highlighter.removeHighlights();
document.getElementById("input-text").value = "";
document.getElementById("output-text").value = "";

54
src/web/LoaderWorker.js Normal file
View File

@@ -0,0 +1,54 @@
/**
* Web Worker to load large amounts of data without locking up the UI.
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
/**
* Respond to message from parent thread.
*/
self.addEventListener("message", function(e) {
const r = e.data;
if (r.hasOwnProperty("file")) {
self.loadFile(r.file);
}
});
/**
* Loads a file object into an ArrayBuffer, then transfers it back to the parent thread.
*
* @param {File} file
*/
self.loadFile = function(file) {
const reader = new FileReader();
let data = new Uint8Array(file.size);
let offset = 0;
const CHUNK_SIZE = 10485760; // 10MiB
const seek = function() {
if (offset >= file.size) {
self.postMessage({"progress": 100});
self.postMessage({"fileBuffer": data.buffer}, [data.buffer]);
return;
}
self.postMessage({"progress": Math.round(offset / file.size * 100)});
const slice = file.slice(offset, offset + CHUNK_SIZE);
reader.readAsArrayBuffer(slice);
};
reader.onload = function(e) {
data.set(new Uint8Array(reader.result), offset);
offset += CHUNK_SIZE;
seek();
};
reader.onerror = function(e) {
self.postMessage({"error": file.error.message});
};
seek();
};

View File

@@ -58,7 +58,7 @@ const Manager = function(app) {
this.ops = new OperationsWaiter(this.app, this);
this.input = new InputWaiter(this.app, this);
this.output = new OutputWaiter(this.app, this);
this.options = new OptionsWaiter(this.app);
this.options = new OptionsWaiter(this.app, this);
this.highlighter = new HighlighterWaiter(this.app, this);
this.seasonal = new SeasonalWaiter(this.app, this);
this.bindings = new BindingsWaiter(this.app, this);
@@ -119,7 +119,7 @@ Manager.prototype.initialiseEventListeners = function() {
this.addDynamicListener(".op-list .op-icon", "mouseover", this.ops.opIconMouseover, this.ops);
this.addDynamicListener(".op-list .op-icon", "mouseleave", this.ops.opIconMouseleave, this.ops);
this.addDynamicListener(".op-list", "oplistcreate", this.ops.opListCreate, this.ops);
this.addDynamicListener("li.operation", "operationadd", this.recipe.opAdd.bind(this.recipe));
this.addDynamicListener("li.operation", "operationadd", this.recipe.opAdd, this.recipe);
// Recipe
this.addDynamicListener(".arg:not(select)", "input", this.recipe.ingChange, this.recipe);
@@ -135,13 +135,14 @@ Manager.prototype.initialiseEventListeners = function() {
this.addMultiEventListener("#input-text", "keyup paste", this.input.inputChange, this.input);
document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app));
document.getElementById("clr-io").addEventListener("click", this.input.clearIoClick.bind(this.input));
document.getElementById("input-text").addEventListener("dragover", this.input.inputDragover.bind(this.input));
document.getElementById("input-text").addEventListener("dragleave", this.input.inputDragleave.bind(this.input));
document.getElementById("input-text").addEventListener("drop", this.input.inputDrop.bind(this.input));
this.addListeners("#input-text,#input-file", "dragover", this.input.inputDragover, this.input);
this.addListeners("#input-text,#input-file", "dragleave", this.input.inputDragleave, this.input);
this.addListeners("#input-text,#input-file", "drop", this.input.inputDrop, this.input);
document.getElementById("input-text").addEventListener("scroll", this.highlighter.inputScroll.bind(this.highlighter));
document.getElementById("input-text").addEventListener("mouseup", this.highlighter.inputMouseup.bind(this.highlighter));
document.getElementById("input-text").addEventListener("mousemove", this.highlighter.inputMousemove.bind(this.highlighter));
this.addMultiEventListener("#input-text", "mousedown dblclick select", this.highlighter.inputMousedown, this.highlighter);
document.querySelector("#input-file .close").addEventListener("click", this.input.clearIoClick.bind(this.input));
// Output
document.getElementById("save-to-file").addEventListener("click", this.output.saveClick.bind(this.output));
@@ -157,6 +158,9 @@ Manager.prototype.initialiseEventListeners = function() {
this.addMultiEventListener("#output-text", "mousedown dblclick select", this.highlighter.outputMousedown, this.highlighter);
this.addMultiEventListener("#output-html", "mousedown dblclick select", this.highlighter.outputHtmlMousedown, this.highlighter);
this.addDynamicListener(".file-switch", "click", this.output.fileSwitch, this.output);
this.addDynamicListener("#output-file-download", "click", this.output.downloadFile, this.output);
this.addDynamicListener("#output-file-slice", "click", this.output.displayFileSlice, this.output);
document.getElementById("show-file-overlay").addEventListener("click", this.output.showFileOverlayClick.bind(this.output));
// Options
document.getElementById("options").addEventListener("click", this.options.optionsClick.bind(this.options));
@@ -168,6 +172,7 @@ Manager.prototype.initialiseEventListeners = function() {
this.addDynamicListener(".option-item input[type=number]", "change", this.options.numberChange, this.options);
this.addDynamicListener(".option-item select", "change", this.options.selectChange, this.options);
document.getElementById("theme").addEventListener("change", this.options.themeChange.bind(this.options));
document.getElementById("logLevel").addEventListener("change", this.options.logLevelChange.bind(this.options));
// Misc
window.addEventListener("keydown", this.bindings.parseInput.bind(this.bindings));

View File

@@ -8,8 +8,9 @@
* @constructor
* @param {App} app - The main view object for CyberChef.
*/
const OptionsWaiter = function(app) {
const OptionsWaiter = function(app, manager) {
this.app = app;
this.manager = manager;
};
@@ -86,6 +87,7 @@ OptionsWaiter.prototype.switchChange = function(e, state) {
const el = e.target;
const option = el.getAttribute("option");
log.debug(`Setting ${option} to ${state}`);
this.app.options[option] = state;
if (this.app.isLocalStorageAvailable())
@@ -102,8 +104,10 @@ OptionsWaiter.prototype.switchChange = function(e, state) {
OptionsWaiter.prototype.numberChange = function(e) {
const el = e.target;
const option = el.getAttribute("option");
const val = parseInt(el.value, 10);
this.app.options[option] = parseInt(el.value, 10);
log.debug(`Setting ${option} to ${val}`);
this.app.options[option] = val;
if (this.app.isLocalStorageAvailable())
localStorage.setItem("options", JSON.stringify(this.app.options));
@@ -120,6 +124,7 @@ OptionsWaiter.prototype.selectChange = function(e) {
const el = e.target;
const option = el.getAttribute("option");
log.debug(`Setting ${option} to ${el.value}`);
this.app.options[option] = el.value;
if (this.app.isLocalStorageAvailable())
@@ -149,6 +154,8 @@ OptionsWaiter.prototype.setWordWrap = function() {
/**
* Changes the theme by setting the class of the <html> element.
*
* @param {Event} e
*/
OptionsWaiter.prototype.themeChange = function (e) {
const themeClass = e.target.value;
@@ -156,4 +163,16 @@ OptionsWaiter.prototype.themeChange = function (e) {
document.querySelector(":root").className = themeClass;
};
/**
* Changes the console logging level.
*
* @param {Event} e
*/
OptionsWaiter.prototype.logLevelChange = function (e) {
const level = e.target.value;
log.setLevel(level, false);
this.manager.worker.setLogLevel();
};
export default OptionsWaiter;

View File

@@ -1,4 +1,5 @@
import Utils from "../core/Utils.js";
import FileSaver from "file-saver";
/**
@@ -15,6 +16,9 @@ import Utils from "../core/Utils.js";
const OutputWaiter = function(app, manager) {
this.app = app;
this.manager = manager;
this.dishBuffer = null;
this.dishStr = null;
};
@@ -31,47 +35,151 @@ OutputWaiter.prototype.get = function() {
/**
* Sets the output in the output textarea.
*
* @param {string} dataStr - The output string/HTML
* @param {string|ArrayBuffer} data - The output string/HTML/ArrayBuffer
* @param {string} type - The data type of the output
* @param {number} duration - The length of time (ms) it took to generate the output
* @param {boolean} [preserveBuffer=false] - Whether to preserve the dishBuffer
*/
OutputWaiter.prototype.set = function(dataStr, type, duration) {
OutputWaiter.prototype.set = function(data, type, duration, preserveBuffer) {
log.debug("Output type: " + type);
const outputText = document.getElementById("output-text");
const outputHtml = document.getElementById("output-html");
const outputFile = document.getElementById("output-file");
const outputHighlighter = document.getElementById("output-highlighter");
const inputHighlighter = document.getElementById("input-highlighter");
let scriptElements, lines, length;
if (type === "html") {
outputText.style.display = "none";
outputHtml.style.display = "block";
outputHighlighter.display = "none";
inputHighlighter.display = "none";
if (!preserveBuffer) {
this.closeFile();
document.getElementById("show-file-overlay").style.display = "none";
}
outputText.value = "";
outputHtml.innerHTML = dataStr;
switch (type) {
case "html":
outputText.style.display = "none";
outputHtml.style.display = "block";
outputFile.style.display = "none";
outputHighlighter.display = "none";
inputHighlighter.display = "none";
// Execute script sections
const scriptElements = outputHtml.querySelectorAll("script");
for (let i = 0; i < scriptElements.length; i++) {
try {
eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval
} catch (err) {
console.error(err);
outputText.value = "";
outputHtml.innerHTML = data;
this.dishStr = Utils.stripHtmlTags(data, true);
length = data.length;
lines = this.dishStr.count("\n") + 1;
// Execute script sections
scriptElements = outputHtml.querySelectorAll("script");
for (let i = 0; i < scriptElements.length; i++) {
try {
eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval
} catch (err) {
log.error(err);
}
}
}
} else {
outputText.style.display = "block";
outputHtml.style.display = "none";
outputHighlighter.display = "block";
inputHighlighter.display = "block";
break;
case "ArrayBuffer":
outputText.style.display = "block";
outputHtml.style.display = "none";
outputHighlighter.display = "none";
inputHighlighter.display = "none";
outputText.value = Utils.printable(dataStr, true);
outputHtml.innerHTML = "";
outputText.value = "";
outputHtml.innerHTML = "";
this.dishStr = "";
length = data.byteLength;
this.setFile(data);
break;
case "string":
default:
outputText.style.display = "block";
outputHtml.style.display = "none";
outputFile.style.display = "none";
outputHighlighter.display = "block";
inputHighlighter.display = "block";
outputText.value = Utils.printable(data, true);
outputHtml.innerHTML = "";
lines = data.count("\n") + 1;
length = data.length;
this.dishStr = data;
break;
}
this.manager.highlighter.removeHighlights();
const lines = dataStr.count("\n") + 1;
this.setOutputInfo(dataStr.length, lines, duration);
this.setOutputInfo(length, lines, duration);
};
/**
* Shows file details.
*
* @param {ArrayBuffer} buf
*/
OutputWaiter.prototype.setFile = function(buf) {
this.dishBuffer = buf;
const file = new File([buf], "output.dat");
// Display file overlay in output area with details
const fileOverlay = document.getElementById("output-file"),
fileSize = document.getElementById("output-file-size");
fileOverlay.style.display = "block";
fileSize.textContent = file.size.toLocaleString() + " bytes";
};
/**
* Removes the output file and nulls its memory.
*/
OutputWaiter.prototype.closeFile = function() {
this.dishBuffer = null;
document.getElementById("output-file").style.display = "none";
};
/**
* Handler for file download events.
*/
OutputWaiter.prototype.downloadFile = function() {
this.filename = window.prompt("Please enter a filename:", this.filename || "download.dat");
const file = new File([this.dishBuffer], this.filename);
if (this.filename) FileSaver.saveAs(file, this.filename, false);
};
/**
* Handler for file slice display events.
*/
OutputWaiter.prototype.displayFileSlice = function() {
const startTime = new Date().getTime(),
showFileOverlay = document.getElementById("show-file-overlay"),
sliceFromEl = document.getElementById("output-file-slice-from"),
sliceToEl = document.getElementById("output-file-slice-to"),
sliceFrom = parseInt(sliceFromEl.value, 10),
sliceTo = parseInt(sliceToEl.value, 10),
str = Utils.arrayBufferToStr(this.dishBuffer.slice(sliceFrom, sliceTo));
showFileOverlay.style.display = "block";
this.set(str, "string", new Date().getTime() - startTime, true);
};
/**
* Handler for show file overlay events.
*
* @param {Event} e
*/
OutputWaiter.prototype.showFileOverlayClick = function(e) {
const outputFile = document.getElementById("output-file"),
showFileOverlay = e.target;
outputFile.style.display = "block";
showFileOverlay.style.display = "none";
this.setOutputInfo(this.dishBuffer.byteLength, null, 0);
};
@@ -86,13 +194,17 @@ OutputWaiter.prototype.setOutputInfo = function(length, lines, duration) {
let width = length.toString().length;
width = width < 4 ? 4 : width;
const lengthStr = Utils.pad(length.toString(), width, " ").replace(/ /g, "&nbsp;");
const linesStr = Utils.pad(lines.toString(), width, " ").replace(/ /g, "&nbsp;");
const timeStr = Utils.pad(duration.toString() + "ms", width, " ").replace(/ /g, "&nbsp;");
const lengthStr = length.toString().padStart(width, " ").replace(/ /g, "&nbsp;");
const timeStr = (duration.toString() + "ms").padStart(width, " ").replace(/ /g, "&nbsp;");
document.getElementById("output-info").innerHTML = "time: " + timeStr +
"<br>length: " + lengthStr +
"<br>lines: " + linesStr;
let msg = "time: " + timeStr + "<br>length: " + lengthStr;
if (typeof lines === "number") {
const linesStr = lines.toString().padStart(width, " ").replace(/ /g, "&nbsp;");
msg += "<br>lines: " + linesStr;
}
document.getElementById("output-info").innerHTML = msg;
document.getElementById("input-selection-info").innerHTML = "";
document.getElementById("output-selection-info").innerHTML = "";
};
@@ -129,24 +241,13 @@ OutputWaiter.prototype.adjustWidth = function() {
/**
* Handler for save click events.
* Saves the current output to a file, downloaded as a URL octet stream.
* Saves the current output to a file.
*/
OutputWaiter.prototype.saveClick = function() {
const data = Utils.toBase64(this.app.dishStr);
const filename = window.prompt("Please enter a filename:", "download.dat");
if (filename) {
const el = document.createElement("a");
el.setAttribute("href", "data:application/octet-stream;base64;charset=utf-8," + data);
el.setAttribute("download", filename);
// Firefox requires that the element be added to the DOM before it can be clicked
el.style.display = "none";
document.body.appendChild(el);
el.click();
el.remove();
if (!this.dishBuffer) {
this.dishBuffer = new Uint8Array(Utils.strToCharcode(this.dishStr)).buffer;
}
this.downloadFile();
};
@@ -165,14 +266,14 @@ OutputWaiter.prototype.copyClick = function() {
textarea.style.height = 0;
textarea.style.border = "none";
textarea.value = this.app.dishStr;
textarea.value = this.dishStr;
document.body.appendChild(textarea);
// Select and copy the contents of this textarea
let success = false;
try {
textarea.select();
success = document.execCommand("copy");
success = this.dishStr && document.execCommand("copy");
} catch (err) {
success = false;
}
@@ -195,7 +296,17 @@ OutputWaiter.prototype.copyClick = function() {
OutputWaiter.prototype.switchClick = function() {
this.switchOrigData = this.manager.input.get();
document.getElementById("undo-switch").disabled = false;
this.app.setInput(this.app.dishStr);
if (this.dishBuffer) {
this.manager.input.setFile(new File([this.dishBuffer], "output.dat"));
this.manager.input.handleLoaderMessage({
data: {
progress: 100,
fileBuffer: this.dishBuffer
}
});
} else {
this.app.setInput(this.dishStr);
}
};
@@ -210,7 +321,7 @@ OutputWaiter.prototype.undoSwitchClick = function() {
/**
* Handler for file switch click events.
* Moves a files data for items created via Utils.displayFilesAsHTML to the input.
* Moves a file's data for items created via Utils.displayFilesAsHTML to the input.
*/
OutputWaiter.prototype.fileSwitch = function(e) {
e.preventDefault();
@@ -282,4 +393,14 @@ OutputWaiter.prototype.setStatusMsg = function(msg) {
el.textContent = msg;
};
/**
* Returns true if the output contains carriage returns
*
* @returns {boolean}
*/
OutputWaiter.prototype.containsCR = function() {
return this.dishStr.indexOf("\r") >= 0;
};
export default OutputWaiter;

View File

@@ -253,7 +253,7 @@ RecipeWaiter.prototype.breakpointClick = function(e) {
*/
RecipeWaiter.prototype.operationDblclick = function(e) {
e.target.remove();
window.dispatchEvent(this.manager.statechange);
this.opRemove(e);
};
@@ -266,7 +266,7 @@ RecipeWaiter.prototype.operationDblclick = function(e) {
*/
RecipeWaiter.prototype.operationChildDblclick = function(e) {
e.target.parentNode.remove();
window.dispatchEvent(this.manager.statechange);
this.opRemove(e);
};
@@ -421,6 +421,7 @@ RecipeWaiter.prototype.dropdownToggleClick = function(e) {
* @param {event} e
*/
RecipeWaiter.prototype.opAdd = function(e) {
log.debug(`'${e.target.querySelector(".arg-title").textContent}' added to recipe`);
window.dispatchEvent(this.manager.statechange);
};
@@ -433,6 +434,7 @@ RecipeWaiter.prototype.opAdd = function(e) {
* @param {event} e
*/
RecipeWaiter.prototype.opRemove = function(e) {
log.debug("Operation removed from recipe");
window.dispatchEvent(this.manager.statechange);
};

View File

@@ -1,4 +1,3 @@
import Utils from "../core/Utils.js";
import ChefWorker from "worker-loader?inline&fallback=false!../core/ChefWorker.js";
/**
@@ -22,8 +21,10 @@ const WorkerWaiter = function(app, manager) {
* Sets up the ChefWorker and associated listeners.
*/
WorkerWaiter.prototype.registerChefWorker = function() {
log.debug("Registering new ChefWorker");
this.chefWorker = new ChefWorker();
this.chefWorker.addEventListener("message", this.handleChefMessage.bind(this));
this.setLogLevel();
let docURL = document.location.href.split(/[#?]/)[0];
const index = docURL.lastIndexOf("/");
@@ -41,8 +42,10 @@ WorkerWaiter.prototype.registerChefWorker = function() {
*/
WorkerWaiter.prototype.handleChefMessage = function(e) {
const r = e.data;
log.debug("Receiving '" + r.action + "' from ChefWorker");
switch (r.action) {
case "bakeSuccess":
case "bakeComplete":
this.bakingComplete(r.data);
break;
case "bakeError":
@@ -53,12 +56,14 @@ WorkerWaiter.prototype.handleChefMessage = function(e) {
break;
case "workerLoaded":
this.app.workerLoaded = true;
log.debug("ChefWorker loaded");
this.app.loaded();
break;
case "statusMessage":
this.manager.output.setStatusMsg(r.data);
break;
case "optionUpdate":
log.debug(`Setting ${r.data.option} to ${r.data.value}`);
this.app.options[r.data.option] = r.data.value;
break;
case "setRegisters":
@@ -68,7 +73,7 @@ WorkerWaiter.prototype.handleChefMessage = function(e) {
this.manager.highlighter.displayHighlights(r.data.pos, r.data.direction);
break;
default:
console.error("Unrecognised message from ChefWorker", e);
log.error("Unrecognised message from ChefWorker", e);
break;
}
};
@@ -111,10 +116,10 @@ WorkerWaiter.prototype.bakingComplete = function(response) {
this.app.handleError(response.error);
}
this.app.dishStr = response.type === "html" ? Utils.stripHtmlTags(response.result, true) : response.result;
this.app.progress = response.progress;
this.manager.recipe.updateBreakpointIndicator(response.progress);
this.manager.output.set(response.result, response.type, response.duration);
log.debug("--- Bake complete ---");
};
@@ -147,7 +152,7 @@ WorkerWaiter.prototype.bake = function(input, recipeConfig, options, progress, s
* Asks the ChefWorker to run a silent bake, forcing the browser to load and cache all the relevant
* JavaScript code needed to do a real bake.
*
* @param {Objectp[]} [recipeConfig]
* @param {Object[]} [recipeConfig]
*/
WorkerWaiter.prototype.silentBake = function(recipeConfig) {
this.chefWorker.postMessage({
@@ -180,4 +185,19 @@ WorkerWaiter.prototype.highlight = function(recipeConfig, direction, pos) {
};
/**
* Sets the console log level in the worker.
*
* @param {string} level
*/
WorkerWaiter.prototype.setLogLevel = function(level) {
if (!this.chefWorker) return;
this.chefWorker.postMessage({
action: "setLogLevel",
data: log.getLevel()
});
};
export default WorkerWaiter;

View File

@@ -181,6 +181,20 @@
<div class="textarea-wrapper no-select">
<div id="input-highlighter" class="no-select"></div>
<textarea id="input-text"></textarea>
<div id="input-file">
<div style="position: relative; height: 100%;">
<div class="card">
<img aria-hidden="true" src="<%- require('../static/images/file-128x128.png') %>" alt="File icon"/>
<div class="card-body">
<button type="button" class="close" id="input-file-close">&times;</button>
Name: <span id="input-file-name"></span><br>
Size: <span id="input-file-size"></span><br>
Type: <span id="input-file-type"></span><br>
Loaded: <span id="input-file-loaded"></span>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -202,6 +216,26 @@
<div id="output-highlighter" class="no-select"></div>
<div id="output-html"></div>
<textarea id="output-text" readonly="readonly"></textarea>
<img id="show-file-overlay" aria-hidden="true" src="<%- require('../static/images/file-32x32.png') %>" alt="Show file overlay" title="Show file overlay"/>
<div id="output-file">
<div style="position: relative; height: 100%;">
<div class="card">
<img aria-hidden="true" src="<%- require('../static/images/file-128x128.png') %>" alt="File icon"/>
<div class="card-body">
Size: <span id="output-file-size"></span><br>
<button id="output-file-download" type="button" class="btn btn-primary">Download</button>
<div class="input-group">
<span class="input-group-btn">
<button id="output-file-slice" type="button" class="btn btn-default" title="View slice">&#x1f50d;</button>
</span>
<input type="number" class="form-control" id="output-file-slice-from" placeholder="From" value="0" step="1024" min="0">
<div class="input-group-addon">to</div>
<input type="number" class="form-control" id="output-file-slice-to" placeholder="To" value="1024" step="1024" min="0">
</div>
</div>
</div>
</div>
</div>
<div id="output-loader">
<div class="loader"></div>
<div class="loading-msg"></div>
@@ -308,31 +342,46 @@
</div>
<div class="option-item">
<input type="checkbox" option="updateUrl" id="updateUrl" checked />
<label for="updateUrl"> Update the URL when the input or recipe changes </label>
<label for="updateUrl"> Update the URL when the input or recipe changes</label>
</div>
<div class="option-item">
<input type="checkbox" option="showHighlighter" id="showHighlighter" checked />
<label for="showHighlighter"> Highlight selected bytes in output and input (when possible) </label>
<label for="showHighlighter"> Highlight selected bytes in output and input (when possible)</label>
</div>
<div class="option-item">
<input type="checkbox" option="treatAsUtf8" id="treatAsUtf8" checked />
<label for="treatAsUtf8"> Treat output as UTF-8 if possible </label>
<label for="treatAsUtf8"> Treat output as UTF-8 if possible</label>
</div>
<div class="option-item">
<input type="checkbox" option="wordWrap" id="wordWrap" checked />
<label for="wordWrap"> Word wrap the input and output </label>
<label for="wordWrap"> Word wrap the input and output</label>
</div>
<div class="option-item">
<input type="checkbox" option="showErrors" id="showErrors" checked />
<label for="showErrors"> Operation error reporting (recommended) </label>
<label for="showErrors"> Operation error reporting (recommended)</label>
</div>
<div class="option-item">
<input type="checkbox" option="useMetaKey" id="useMetaKey" />
<label for="useMetaKey"> Use meta key for keybindings (Windows ⊞/Command ⌘) </label>
<label for="useMetaKey"> Use meta key for keybindings (Windows ⊞/Command ⌘)</label>
</div>
<div class="option-item">
<input type="number" option="errorTimeout" id="errorTimeout" />
<label for="errorTimeout"> Operation error timeout in ms (0 for never) </label>
<label for="errorTimeout"> Operation error timeout in ms (0 for never)</label>
</div>
<div class="option-item">
<input type="number" option="outputFileThreshold" id="outputFileThreshold" />
<label for="outputFileThreshold"> Size threshold for treating the output as a file (KiB)</label>
</div>
<div class="option-item">
<select option="logLevel" id="logLevel">
<option value="silent">Silent</option>
<option value="error">Error</option>
<option value="warn">Warn</option>
<option value="info">Info</option>
<option value="debug">Debug</option>
<option value="trace">Trace</option>
</select>
<label for="logLevel"> Console logging level</label>
</div>
</div>
<div class="modal-footer">
@@ -438,8 +487,9 @@
</a>
</blockquote>
<div class="collapse" id="faq-load-files">
<p>Yes! Just drag your file over the input box and drop it. The contents of the file will be converted into hexadecimal and the 'From Hex' operation will be added to the beginning of the recipe (if it's not already there). This is so that special characters like carriage returns aren't removed by your browser.</p>
<p>Please note that loading large files is likely to cause a crash. There's not a lot that can be done about this - browsers just aren't very good at handling and displaying large amounts of data.</p>
<p>Yes! Just drag your file over the input box and drop it.</p>
<p>CyberChef can handle files up to around 500MB (depending on your browser), however some of the operations may take a very long time to run over this much data.</p>
<p>If the output is larger than a certain threshold (default 1MiB), it will be presented to you as a file available for download. Slices of the file can be viewed in the output if you need to inspect them.</p>
</div>
<blockquote>
<a data-toggle="collapse" data-target="#faq-fork">
@@ -482,7 +532,7 @@
<br>
<p>There are around 150 useful operations in CyberChef for anyone working on anything vaguely Internet-related, whether you just want to convert a timestamp to a different format, decompress gzipped data, create a SHA3 hash, or parse an X.509 certificate to find out who issued it.</p>
<p>There are around 200 useful operations in CyberChef for anyone working on anything vaguely Internet-related, whether you just want to convert a timestamp to a different format, decompress gzipped data, create a SHA3 hash, or parse an X.509 certificate to find out who issued it.</p>
<p>Its the Cyber Swiss Army Knife.</p>
</div>
<div role="tabpanel" class="tab-pane" id="keybindings" style="padding: 20px;">

View File

@@ -38,15 +38,17 @@ function main() {
];
const defaultOptions = {
updateUrl: true,
showHighlighter: true,
treatAsUtf8: true,
wordWrap: true,
showErrors: true,
errorTimeout: 4000,
attemptHighlight: true,
theme: "classic",
useMetaKey: false
updateUrl: true,
showHighlighter: true,
treatAsUtf8: true,
wordWrap: true,
showErrors: true,
errorTimeout: 4000,
attemptHighlight: true,
theme: "classic",
useMetaKey: false,
outputFileThreshold: 1024,
logLevel: "info"
};
document.removeEventListener("DOMContentLoaded", main, false);
@@ -54,9 +56,6 @@ function main() {
window.app.setup();
}
// Fix issues with browsers that don't support console.log()
window.console = console || {log: function() {}, error: function() {}};
window.compileTime = moment.tz(COMPILE_TIME, "DD/MM/YYYY HH:mm:ss z", "UTC").valueOf();
window.compileMessage = COMPILE_MSG;

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -28,3 +28,56 @@
margin: 0;
padding: 0;
}
.card {
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
transition: 0.3s;
width: 400px;
height: 150px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-family: var(--primary-font-family);
color: var(--primary-font-colour);
line-height: 30px;
background-color: var(--primary-background-colour);
}
.card:hover {
box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);
}
.card>img {
float: left;
width: 128px;
height: 128px;
margin-left: 10px;
margin-top: 11px;
}
.card-body .close {
position: absolute;
right: 10px;
top: 10px;
}
.card-body {
float: left;
padding: 16px;
width: 250px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
user-select: text;
}
.card-body>.btn {
margin-bottom: 15px;
margin-top: 5px;
}
.card input[type=number] {
padding-right: 6px;
padding-left: 6px;
}

View File

@@ -39,7 +39,7 @@
}
.textarea-wrapper textarea,
.textarea-wrapper div {
.textarea-wrapper>div {
font-family: var(--fixed-width-font-family);
font-size: var(--fixed-width-font-size);
color: var(--fixed-width-font-colour);
@@ -77,6 +77,25 @@
transition: all 0.5s ease;
}
#input-file,
#output-file {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: var(--title-background-colour);
display: none;
}
#show-file-overlay {
position: absolute;
right: 15px;
top: 15px;
cursor: pointer;
display: none;
}
.io-btn-group {
float: right;
margin-top: -4px;
@@ -92,6 +111,8 @@
font-family: var(--fixed-width-font-family);
font-weight: normal;
font-size: 8pt;
display: flex;
align-items: center;
}
#input-info {

View File

@@ -64,7 +64,8 @@ a:focus {
.alert,
.modal-content,
.tooltip-inner,
.dropdown-menu {
.dropdown-menu,
.input-group-addon {
border-radius: 0 !important;
}
@@ -187,6 +188,15 @@ optgroup {
color: var(--primary-font-colour);
}
.input-group-addon:not(:first-child):not(:last-child) {
border-left: 0;
border-right: 0;
}
.input-group-btn:first-child>.btn {
border-right: 0;
}
/* Bootstrap-switch */

View File

@@ -1,3 +1,5 @@
/* eslint no-console: 0 */
/**
* TestRunner.js
*

File diff suppressed because it is too large Load Diff

View File

@@ -35,7 +35,8 @@ module.exports = {
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery",
moment: "moment-timezone"
moment: "moment-timezone",
log: "loglevel"
}),
new webpack.BannerPlugin({
banner: banner,