From 638e03856b8e1d1b73a80127825672b4744cf699 Mon Sep 17 00:00:00 2001 From: Matt C Date: Tue, 17 Oct 2017 19:36:51 +0100 Subject: [PATCH 1/2] Initial keybinding functionality + documentation Todo: - allow user to specify whether to use alt or meta key (relatively easy to implement) - keybinding icon for about pane --- src/web/BindingsWaiter.js | 118 ++++++++++++++++++++++++++++++++++++ src/web/Manager.js | 4 +- src/web/html/index.html | 124 +++++++++++++++++++++++++++++++------- 3 files changed, 222 insertions(+), 24 deletions(-) create mode 100644 src/web/BindingsWaiter.js diff --git a/src/web/BindingsWaiter.js b/src/web/BindingsWaiter.js new file mode 100644 index 00000000..8c06e18a --- /dev/null +++ b/src/web/BindingsWaiter.js @@ -0,0 +1,118 @@ +/** + * Waiter to handle keybindings to CyberChef functions (i.e. Bake, Step, Save, Load etc.) + * + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * @constructor + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ +const BindingsWaiter = function (app, manager) { + this.app = app; + this.manager = manager; +}; + + +/** + * Handler for all keydown events + * Checks whether valid keyboard shortcut has been instated + * + * @fires Manager#statechange + * @param {event} e + */ +BindingsWaiter.prototype.parseInput = function(e) { + if (e.ctrlKey && e.altKey) { + let elem; + switch (e.code) { + case "KeyF": + e.preventDefault(); + document.getElementById("search").focus(); // Focus search + break; + case "KeyI": + e.preventDefault(); + document.getElementById("input-text").focus(); // Focus input + break; + case "KeyO": + e.preventDefault(); + document.getElementById("output-text").focus(); // Focus output + break; + case "Period": + try { + elem = document.activeElement.closest(".operation"); + if (elem.parentNode.lastChild === elem) { + elem.parentNode.firstChild.querySelectorAll(".arg")[0].focus(); // if operation is last in recipe, loop around to the top operation's first argument + } else { + elem.nextSibling.querySelectorAll(".arg")[0].focus(); //focus first argument of next operation + } + } catch (e) { + // do nothing, just don't throw an error + } + break; + case "KeyB": + try { + elem = document.activeElement.closest(".operation").querySelectorAll(".breakpoint")[0]; + if (elem.getAttribute("break") === "false") { + elem.setAttribute("break", "true"); // add break point if not already enabled + elem.classList.add("breakpoint-selected"); + } else { + elem.setAttribute("break", "false"); // remove break point if already enabled + elem.classList.remove("breakpoint-selected"); + } + window.dispatchEvent(this.manager.statechange); + } catch (e) { + // do nothing, just don't throw an error + } + break; + case "KeyD": + try { + elem = document.activeElement.closest(".operation").querySelectorAll(".disable-icon")[0]; + if (elem.getAttribute("disabled") === "false") { + elem.setAttribute("disabled", "true"); // disable operation if enabled + elem.classList.add("disable-elem-selected"); + elem.parentNode.parentNode.classList.add("disabled"); + } else { + elem.setAttribute("disabled", "false"); // enable operation if disabled + elem.classList.remove("disable-elem-selected"); + elem.parentNode.parentNode.classList.remove("disabled"); + } + this.app.progress = 0; + window.dispatchEvent(this.manager.statechange); + } catch (e) { + // do nothing, just don't throw an error + } + break; + case "Space": + this.app.bake(); // bake the recipe + break; + case "Quote": + this.app.bake(true); // step through the recipe + break; + case "KeyC": + this.manager.recipe.clearRecipe(); // clear recipe + break; + case "KeyS": + this.manager.output.saveClick(); // save output to file + break; + case "KeyL": + this.manager.controls.loadClick(); // load a recipe + break; + case "KeyM": + this.manager.controls.switchClick(); // switch input & output + break; + default: + if (e.code.match(/Digit[0-9]/g)) { + e.preventDefault(); + try { + document.querySelector(`li:nth-child(${e.code.substr(-1)}) .arg`).focus(); // select the first argument of the operation corresponding to the number pressed + } catch (e) { + // do nothing, just don't throw an error + } + } + break; + } + } +}; + +export default BindingsWaiter; diff --git a/src/web/Manager.js b/src/web/Manager.js index 1d32758c..de1df8e4 100755 --- a/src/web/Manager.js +++ b/src/web/Manager.js @@ -8,6 +8,7 @@ import OutputWaiter from "./OutputWaiter.js"; import OptionsWaiter from "./OptionsWaiter.js"; import HighlighterWaiter from "./HighlighterWaiter.js"; import SeasonalWaiter from "./SeasonalWaiter.js"; +import BindingsWaiter from "./BindingsWaiter.js"; /** @@ -60,6 +61,7 @@ const Manager = function(app) { this.options = new OptionsWaiter(this.app); this.highlighter = new HighlighterWaiter(this.app, this); this.seasonal = new SeasonalWaiter(this.app, this); + this.bindings = new BindingsWaiter(this.app, this); // Object to store dynamic handlers to fire on elements that may not exist yet this.dynamicHandlers = {}; @@ -89,7 +91,7 @@ Manager.prototype.initialiseEventListeners = function() { window.addEventListener("focus", this.window.windowFocus.bind(this.window)); window.addEventListener("statechange", this.app.stateChange.bind(this.app)); window.addEventListener("popstate", this.app.popState.bind(this.app)); - + window.addEventListener("keydown", this.bindings.parseInput.bind(this.bindings)); // Controls document.getElementById("bake").addEventListener("click", this.controls.bakeClick.bind(this.controls)); document.getElementById("auto-bake").addEventListener("change", this.controls.autoBakeChange.bind(this.controls)); diff --git a/src/web/html/index.html b/src/web/html/index.html index 8d23aed4..e1f300e7 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -1,17 +1,17 @@ + + Keybindings +
@@ -448,20 +453,20 @@
What

A simple, intuitive web app for analysing and decoding data without having to deal with complex tools or programming languages. CyberChef encourages both technical and non-technical people to explore data formats, encryption and compression.


- +
Why

Digital data comes in all shapes, sizes and formats in the modern world – CyberChef helps to make sense of this data all on one easy-to-use platform.


- +
How

The interface is designed with simplicity at its heart. Complex techniques are now as trivial as drag-and-drop. Simple functions can be combined to build up a "recipe", potentially resulting in complex analysis, which can be shared with other users and used with their input.

For those comfortable writing code, CyberChef is a quick and efficient way to prototype solutions to a problem which can then be scripted once proven to work.


- +
Who

It is expected that CyberChef will be useful for cybersecurity and antivirus companies. It should also appeal to the academic world and any individuals or companies involved in the analysis of digital data, be that software developers, analysts, mathematicians or casual puzzle solvers.


- +
Aim

It is hoped that by releasing CyberChef through GitHub, contributions can be added which can be rolled out into future versions of the tool.


@@ -470,6 +475,79 @@

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.

It’s the Cyber Swiss Army Knife.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CommandShortcut (Win/Linux)Shortcut (Mac)
Place cursor in search fieldCtrl+Alt+fCtrl+Opt+f
Place cursor in input boxCtrl+Alt+iCtrl+Opt+i
Place cursor in output boxCtrl+Alt+oCtrl+Opt+o
Place cursor in first argument field
of the next operation in the recipe
Ctrl+Alt+.Ctrl+Opt+.
Place cursor in first argument field
of the nth operation in the recipe
Ctrl+Alt+[1-9]Ctrl+Opt+[1-9]
Disable current operationCtrl+Alt+dCtrl+Opt+d
Set/clear breakpointCtrl+Alt+bCtrl+Opt+b
BakeCtrl+Alt+SpaceCtrl+Opt+Space
StepCtrl+Alt+'Ctrl+Opt+'
Clear recipeCtrl+Alt+cCtrl+Opt+c
Save to fileCtrl+Alt+sCtrl+Opt+s
Load recipeCtrl+Alt+lCtrl+Opt+l
Move output to inputCtrl+Alt+mCtrl+Opt+m
+
@@ -482,7 +560,7 @@ - + - + From 8b30fdf7f1d33943ddfeedf9b4adc259d852fee1 Mon Sep 17 00:00:00 2001 From: Matt C Date: Sat, 4 Nov 2017 12:55:28 +0000 Subject: [PATCH 2/2] Adds ability for user to use Meta key instead of alt for keybindings - includes dynamically updating keybinding list --- src/web/BindingsWaiter.js | 88 ++++++++++++++++++++++++++++++++++++++- src/web/Manager.js | 6 ++- src/web/html/index.html | 79 +++-------------------------------- src/web/index.js | 1 + 4 files changed, 99 insertions(+), 75 deletions(-) diff --git a/src/web/BindingsWaiter.js b/src/web/BindingsWaiter.js index 8c06e18a..2454ed59 100644 --- a/src/web/BindingsWaiter.js +++ b/src/web/BindingsWaiter.js @@ -23,7 +23,9 @@ const BindingsWaiter = function (app, manager) { * @param {event} e */ BindingsWaiter.prototype.parseInput = function(e) { - if (e.ctrlKey && e.altKey) { + let modKey = e.altKey; + if (this.app.options.useMetaKey) modKey = e.metaKey; + if (e.ctrlKey && modKey) { let elem; switch (e.code) { case "KeyF": @@ -115,4 +117,88 @@ BindingsWaiter.prototype.parseInput = function(e) { } }; +/** + * Updates keybinding list when metaKey option is toggled + * + */ +BindingsWaiter.prototype.updateKeybList = function() { + let modWinLin = "Alt"; + let modMac = "Opt"; + if (this.app.options.useMetaKey) { + modWinLin = "Win"; + modMac = "Cmd"; + } + document.getElementById("keybList").innerHTML = ` + + Command + Shortcut (Win/Linux) + Shortcut (Mac) + + + Place cursor in search field + Ctrl+${modWinLin}+f + Ctrl+${modMac}+f + + Place cursor in input box + Ctrl+${modWinLin}+i + Ctrl+${modMac}+i + + + Place cursor in output box + Ctrl+${modWinLin}+o + Ctrl+${modMac}+o + + + Place cursor in first argument field
of the next operation in the recipe + Ctrl+${modWinLin}+. + Ctrl+${modMac}+. + + + Place cursor in first argument field
of the nth operation in the recipe + Ctrl+${modWinLin}+[1-9] + Ctrl+${modMac}+[1-9] + + + Disable current operation + Ctrl+${modWinLin}+d + Ctrl+${modMac}+d + + + Set/clear breakpoint + Ctrl+${modWinLin}+b + Ctrl+${modMac}+b + + + Bake + Ctrl+${modWinLin}+Space + Ctrl+${modMac}+Space + + + Step + Ctrl+${modWinLin}+' + Ctrl+${modMac}+' + + + Clear recipe + Ctrl+${modWinLin}+c + Ctrl+${modMac}+c + + + Save to file + Ctrl+${modWinLin}+s + Ctrl+${modMac}+s + + + Load recipe + Ctrl+${modWinLin}+l + Ctrl+${modMac}+l + + + Move output to input + Ctrl+${modWinLin}+m + Ctrl+${modMac}+m + + `; +}; + export default BindingsWaiter; diff --git a/src/web/Manager.js b/src/web/Manager.js index de1df8e4..e0a0ef37 100755 --- a/src/web/Manager.js +++ b/src/web/Manager.js @@ -78,6 +78,7 @@ Manager.prototype.setup = function() { this.recipe.initialiseOperationDragNDrop(); this.controls.autoBakeChange(); this.seasonal.load(); + this.bindings.updateKeybList(); }; @@ -91,7 +92,6 @@ Manager.prototype.initialiseEventListeners = function() { window.addEventListener("focus", this.window.windowFocus.bind(this.window)); window.addEventListener("statechange", this.app.stateChange.bind(this.app)); window.addEventListener("popstate", this.app.popState.bind(this.app)); - window.addEventListener("keydown", this.bindings.parseInput.bind(this.bindings)); // Controls document.getElementById("bake").addEventListener("click", this.controls.bakeClick.bind(this.controls)); document.getElementById("auto-bake").addEventListener("change", this.controls.autoBakeChange.bind(this.controls)); @@ -166,6 +166,10 @@ Manager.prototype.initialiseEventListeners = function() { this.addDynamicListener(".option-item select", "change", this.options.selectChange, this.options); document.getElementById("theme").addEventListener("change", this.options.themeChange.bind(this.options)); + //Keybindings + window.addEventListener("keydown", this.bindings.parseInput.bind(this.bindings)); + $(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox#useMetaKey", this.bindings.updateKeybList.bind(this.bindings)); + // Misc document.getElementById("alert-close").addEventListener("click", this.app.alertCloseClick.bind(this.app)); }; diff --git a/src/web/html/index.html b/src/web/html/index.html index c968ebfe..0bf52c53 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -325,6 +325,10 @@ +
+ + +
@@ -402,8 +406,7 @@ About
  • - - + Keybindings
  • @@ -482,77 +485,7 @@

    It’s the Cyber Swiss Army Knife.

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    CommandShortcut (Win/Linux)Shortcut (Mac)
    Place cursor in search fieldCtrl+Alt+fCtrl+Opt+f
    Place cursor in input boxCtrl+Alt+iCtrl+Opt+i
    Place cursor in output boxCtrl+Alt+oCtrl+Opt+o
    Place cursor in first argument field
    of the next operation in the recipe
    Ctrl+Alt+.Ctrl+Opt+.
    Place cursor in first argument field
    of the nth operation in the recipe
    Ctrl+Alt+[1-9]Ctrl+Opt+[1-9]
    Disable current operationCtrl+Alt+dCtrl+Opt+d
    Set/clear breakpointCtrl+Alt+bCtrl+Opt+b
    BakeCtrl+Alt+SpaceCtrl+Opt+Space
    StepCtrl+Alt+'Ctrl+Opt+'
    Clear recipeCtrl+Alt+cCtrl+Opt+c
    Save to fileCtrl+Alt+sCtrl+Opt+s
    Load recipeCtrl+Alt+lCtrl+Opt+l
    Move output to inputCtrl+Alt+mCtrl+Opt+m
    +
    diff --git a/src/web/index.js b/src/web/index.js index 80da5733..5964b1e8 100755 --- a/src/web/index.js +++ b/src/web/index.js @@ -46,6 +46,7 @@ function main() { errorTimeout: 4000, attemptHighlight: true, theme: "classic", + useMetaKey: false }; document.removeEventListener("DOMContentLoaded", main, false);