ソースを参照

Merged master into esm branch

n1474335 7 年 前
コミット
a98d37e61c

+ 1 - 1
Gruntfile.js

@@ -280,7 +280,7 @@ module.exports = function (grunt) {
                     chunks: false,
                     modules: false,
                     entrypoints: false,
-                    warningsFilter: /source-map/,
+                    warningsFilter: [/source-map/, /dependency is an expression/],
                 }
             },
             start: {

ファイルの差分が大きいため隠しています
+ 270 - 337
package-lock.json


+ 30 - 28
package.json

@@ -1,6 +1,6 @@
 {
   "name": "cyberchef",
-  "version": "7.9.0",
+  "version": "7.11.1",
   "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
   "author": "n1474335 <n1474335@gmail.com>",
   "homepage": "https://gchq.github.io/CyberChef",
@@ -30,14 +30,14 @@
   "main": "build/node/CyberChef.js",
   "bugs": "https://github.com/gchq/CyberChef/issues",
   "devDependencies": {
-    "babel-core": "^6.26.0",
-    "babel-loader": "^7.1.3",
+    "babel-core": "^6.26.3",
+    "babel-loader": "^7.1.4",
     "babel-preset-env": "^1.6.1",
-    "css-loader": "^0.28.10",
-    "eslint": "^4.18.1",
+    "css-loader": "^0.28.11",
+    "eslint": "^4.19.1",
     "exports-loader": "^0.7.0",
     "extract-text-webpack-plugin": "^4.0.0-alpha0",
-    "file-loader": "^1.1.10",
+    "file-loader": "^1.1.11",
     "grunt": ">=1.0.2",
     "grunt-accessibility": "~6.0.0",
     "grunt-chmod": "~1.1.1",
@@ -48,23 +48,23 @@
     "grunt-eslint": "^20.1.0",
     "grunt-exec": "~3.0.0",
     "grunt-jsdoc": "^2.2.1",
-    "grunt-webpack": "^3.0.2",
-    "html-webpack-plugin": "^3.0.4",
+    "grunt-webpack": "^3.1.1",
+    "html-webpack-plugin": "^3.2.0",
     "imports-loader": "^0.8.0",
     "ink-docstrap": "^1.3.2",
-    "jsdoc-babel": "^0.3.0",
-    "less": "^3.0.1",
-    "less-loader": "^4.0.6",
-    "postcss-css-variables": "^0.8.0",
+    "jsdoc-babel": "^0.4.0",
+    "less": "^3.0.2",
+    "less-loader": "^4.1.0",
+    "postcss-css-variables": "^0.8.1",
     "postcss-import": "^11.1.0",
-    "postcss-loader": "^2.1.1",
+    "postcss-loader": "^2.1.4",
     "sitemap": "^1.13.0",
-    "style-loader": "^0.20.2",
-    "url-loader": "^0.6.2",
+    "style-loader": "^0.21.0",
+    "url-loader": "^1.0.1",
     "web-resource-inliner": "^4.2.1",
-    "webpack": "^4.0.1",
-    "webpack-dev-server": "^3.1.0",
-    "webpack-node-externals": "^1.6.0",
+    "webpack": "^4.6.0",
+    "webpack-dev-server": "^3.1.3",
+    "webpack-node-externals": "^1.7.2",
     "webpack-synchronizable-shell-plugin": "0.0.7",
     "worker-loader": "^1.1.1"
   },
@@ -72,20 +72,21 @@
     "babel-polyfill": "^6.26.0",
     "babel-plugin-transform-builtin-extend": "1.1.2",
     "bcryptjs": "^2.4.3",
-    "bignumber.js": "^6.0.0",
+    "bignumber.js": "^7.0.1",
     "bootstrap": "^3.3.7",
     "bootstrap-colorpicker": "^2.5.2",
     "bootstrap-switch": "^3.3.4",
-    "bson": "^2.0.4",
+    "bson": "^2.0.6",
     "crypto-api": "^0.8.0",
     "crypto-js": "^3.1.9-1",
     "ctph.js": "0.0.5",
-    "diff": "^3.4.0",
+    "diff": "^3.5.0",
     "escodegen": "^1.9.1",
+    "es6-promisify": "^6.0.0",
     "esmangle": "^1.0.1",
     "esprima": "^4.0.0",
     "exif-parser": "^0.1.12",
-    "file-saver": "^1.3.3",
+    "file-saver": "^1.3.8",
     "highlight.js": "^9.12.0",
     "jquery": "^3.3.1",
     "js-crc": "^0.2.0",
@@ -93,15 +94,16 @@
     "jsbn": "^1.1.0",
     "jsesc": "^2.5.1",
     "jsonpath": "^1.0.0",
-    "jsrsasign": "8.0.6",
-    "lodash": "^4.17.5",
+    "jsrsasign": "8.0.12",
+    "lodash": "^4.17.10",
     "loglevel": "^1.6.1",
+    "kbpgp": "^2.0.77",
     "loglevel-message-prefix": "^3.0.0",
-    "moment": "^2.20.1",
-    "moment-timezone": "^0.5.14",
-    "node-forge": "^0.7.2",
+    "moment": "^2.22.1",
+    "moment-timezone": "^0.5.16",
+    "node-forge": "^0.7.5",
     "node-md6": "^0.1.0",
-    "nwmatcher": "^1.4.3",
+    "nwmatcher": "^1.4.4",
     "otp": "^0.1.3",
     "scryptsy": "^2.0.0",
     "sladex-blowfish": "^0.8.1",

+ 16 - 9
src/core/Utils.mjs

@@ -524,36 +524,43 @@ class Utils {
      * Parses CSV data and returns it as a two dimensional array or strings.
      *
      * @param {string} data
+     * @param {string[]} [cellDelims=[","]]
+     * @param {string[]} [lineDelims=["\n", "\r"]]
      * @returns {string[][]}
      *
      * @example
      * // returns [["head1", "head2"], ["data1", "data2"]]
      * Utils.parseCSV("head1,head2\ndata1,data2");
      */
-    static parseCSV(data) {
+    static parseCSV(data, cellDelims=[","], lineDelims=["\n", "\r"]) {
         let b,
-            ignoreNext = false,
+            next,
+            renderNext = false,
             inString = false,
             cell = "",
             line = [];
         const lines = [];
 
+        // Remove BOM, often present in Excel CSV files
+        if (data.length && data[0] === "\uFEFF") data = data.substr(1);
+
         for (let i = 0; i < data.length; i++) {
             b = data[i];
-            if (ignoreNext) {
+            next = data[i+1] || "";
+            if (renderNext) {
                 cell += b;
-                ignoreNext = false;
+                renderNext = false;
             } else if (b === "\\") {
-                cell += b;
-                ignoreNext = true;
+                renderNext = true;
             } else if (b === "\"" && !inString) {
                 inString = true;
             } else if (b === "\"" && inString) {
-                inString = false;
-            } else if (b === "," && !inString) {
+                if (next === "\"") renderNext = true;
+                else inString = false;
+            } else if (!inString && cellDelims.indexOf(b) >= 0) {
                 line.push(cell);
                 cell = "";
-            } else if ((b === "\n" || b === "\r") && !inString) {
+            } else if (!inString && lineDelims.indexOf(b) >= 0) {
                 line.push(cell);
                 cell = "";
                 lines.push(line);

+ 7 - 1
src/core/config/Categories.json

@@ -94,7 +94,12 @@
             "PEM to Hex",
             "Hex to PEM",
             "Hex to Object Identifier",
-            "Object Identifier to Hex"
+            "Object Identifier to Hex",
+            "Generate PGP Key Pair",
+            "PGP Encrypt",
+            "PGP Decrypt",
+            "PGP Encrypt and Sign",
+            "PGP Decrypt and Verify"
         ]
     },
     {
@@ -164,6 +169,7 @@
             "To Lower case",
             "Add line numbers",
             "Remove line numbers",
+            "To Table",
             "Reverse",
             "Sort",
             "Unique",

+ 175 - 3
src/core/config/scripts/portOperation.mjs

@@ -672,6 +672,34 @@ const OP_CONFIG = {
             }
         ]
     },
+    "To Table": {
+        module: "Default",
+        description: "Data can be split on different characters and rendered as an HTML or ASCII table with an optional header row.<br><br>Supports the CSV (Comma Separated Values) file format by default. Change the cell delimiter argument to <code>\\t</code> to support TSV (Tab Separated Values) or <code>|</code> for PSV (Pipe Separated Values).<br><br>You can enter as many delimiters as you like. Each character will be treat as a separate possible delimiter.",
+        inputType: "string",
+        outputType: "html",
+        args: [
+            {
+                name: "Cell delimiters",
+                type: "binaryShortString",
+                value: ","
+            },
+            {
+                name: "Row delimiters",
+                type: "binaryShortString",
+                value: "\\n\\r"
+            },
+            {
+                name: "Make first row header",
+                type: "boolean",
+                value: false
+            },
+            {
+                name: "Format",
+                type: "option",
+                value: "ToTable.FORMATS"
+            }
+        ]
+    },
     "From Hex": {
         module: "Default",
         description: "Converts a hexadecimal byte string back into its raw value.<br><br>e.g. <code>ce 93 ce b5 ce b9 ce ac 20 cf 83 ce bf cf 85 0a</code> becomes the UTF-8 encoded string <code>Γειά σου</code>",
@@ -3363,14 +3391,14 @@ const OP_CONFIG = {
     "CRC-32 Checksum": {
         module: "Hashing",
         description: "A cyclic redundancy check (CRC) is an error-detecting code commonly used in digital networks and storage devices to detect accidental changes to raw data.<br><br>The CRC was invented by W. Wesley Peterson in 1961; the 32-bit CRC function of Ethernet and many other standards is the work of several researchers and was published in 1975.",
-        inputType: "string",
+        inputType: "ArrayBuffer",
         outputType: "string",
         args: []
     },
     "CRC-16 Checksum": {
         module: "Hashing",
         description: "A cyclic redundancy check (CRC) is an error-detecting code commonly used in digital networks and storage devices to detect accidental changes to raw data.<br><br>The CRC was invented by W. Wesley Peterson in 1961.",
-        inputType: "string",
+        inputType: "ArrayBuffer",
         outputType: "string",
         args: []
     },
@@ -3423,7 +3451,7 @@ const OP_CONFIG = {
     },
     "Parse X.509 certificate": {
         module: "PublicKey",
-        description: "X.509 is an ITU-T standard for a public key infrastructure (PKI) and Privilege Management Infrastructure (PMI). It is commonly involved with SSL/TLS security.<br><br>This operation displays the contents of a certificate in a human readable format, similar to the openssl command line tool.",
+        description: "X.509 is an ITU-T standard for a public key infrastructure (PKI) and Privilege Management Infrastructure (PMI). It is commonly involved with SSL/TLS security.<br><br>This operation displays the contents of a certificate in a human readable format, similar to the openssl command line tool.<br><br>Tags: X509, server hello, handshake",
         inputType: "string",
         outputType: "string",
         args: [
@@ -4219,6 +4247,150 @@ const OP_CONFIG = {
         outputType: "string",
         args: []
     },
+    "Generate PGP Key Pair": {
+        module: "PGP",
+        description: "Generates a new public/private PGP key pair. Supports RSA and Eliptic Curve (EC) keys.",
+        inputType: "string",
+        outputType: "string",
+        args: [
+            {
+                name: "Key type",
+                type: "option",
+                value: "PGP.KEY_TYPES"
+            },
+            {
+                name: "Password (optional)",
+                type: "string",
+                value: ""
+            },
+            {
+                name: "Name (optional)",
+                type: "string",
+                value: ""
+            },
+            {
+                name: "Email (optional)",
+                type: "string",
+                value: ""
+            },
+        ]
+    },
+    "PGP Encrypt": {
+        module: "PGP",
+        description: [
+            "Input: the message you want to encrypt.",
+            "<br><br>",
+            "Arguments: the ASCII-armoured PGP public key of the recipient.",
+            "<br><br>",
+            "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.",
+            "<br><br>",
+            "This function uses the Keybase implementation of PGP.",
+        ].join("\n"),
+        inputType: "string",
+        outputType: "string",
+        args: [
+            {
+                name: "Public key of recipient",
+                type: "text",
+                value: ""
+            },
+        ]
+    },
+    "PGP Decrypt": {
+        module: "PGP",
+        description: [
+            "Input: the ASCII-armoured PGP message you want to decrypt.",
+            "<br><br>",
+            "Arguments: the ASCII-armoured PGP private key of the recipient, ",
+            "(and the private key password if necessary).",
+            "<br><br>",
+            "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.",
+            "<br><br>",
+            "This function uses the Keybase implementation of PGP.",
+        ].join("\n"),
+        inputType: "string",
+        outputType: "string",
+        args: [
+            {
+                name: "Private key of recipient",
+                type: "text",
+                value: ""
+            },
+            {
+                name: "Private key passphrase",
+                type: "string",
+                value: ""
+            },
+        ]
+    },
+    "PGP Encrypt and Sign": {
+        module: "PGP",
+        description: [
+            "Input: the cleartext you want to sign.",
+            "<br><br>",
+            "Arguments: the ASCII-armoured private key of the signer (plus the private key password if necessary)",
+            "and the ASCII-armoured PGP public key of the recipient.",
+            "<br><br>",
+            "This operation uses PGP to produce an encrypted digital signature.",
+            "<br><br>",
+            "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.",
+            "<br><br>",
+            "This function uses the Keybase implementation of PGP.",
+        ].join("\n"),
+        inputType: "string",
+        outputType: "string",
+        args: [
+            {
+                name: "Private key of signer",
+                type: "text",
+                value: ""
+            },
+            {
+                name: "Private key passphrase",
+                type: "string",
+                value: ""
+            },
+            {
+                name: "Public key of recipient",
+                type: "text",
+                value: ""
+            },
+        ]
+    },
+    "PGP Decrypt and Verify": {
+        module: "PGP",
+        description: [
+            "Input: the ASCII-armoured encrypted PGP message you want to verify.",
+            "<br><br>",
+            "Arguments: the ASCII-armoured PGP public key of the signer, ",
+            "the ASCII-armoured private key of the recipient (and the private key password if necessary).",
+            "<br><br>",
+            "This operation uses PGP to decrypt and verify an encrypted digital signature.",
+            "<br><br>",
+            "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.",
+            "<br><br>",
+            "This function uses the Keybase implementation of PGP.",
+        ].join("\n"),
+        inputType: "string",
+        outputType: "string",
+        args: [
+            {
+                name: "Public key of signer",
+                type: "text",
+                value: "",
+            },
+            {
+                name: "Private key of recipient",
+                type: "text",
+                value: "",
+            },
+            {
+                name: "Private key password",
+                type: "string",
+                value: "",
+            },
+        ]
+    },
 };
 
 main();

+ 10 - 4
src/core/lib/Hex.mjs

@@ -84,9 +84,9 @@ export function toHexFast(data) {
  * fromHex("0a:14:1e", "Colon");
  */
 export function fromHex(data, delim, byteLen=2) {
-    delim = delim || (data.indexOf(" ") >= 0 ? "Space" : "None");
+    delim = delim || "Auto";
     if (delim !== "None") {
-        const delimRegex = Utils.regexRep(delim);
+        const delimRegex = delim === "Auto" ? /[^a-f\d]/gi : Utils.regexRep(delim);
         data = data.replace(delimRegex, "");
     }
 
@@ -99,6 +99,12 @@ export function fromHex(data, delim, byteLen=2) {
 
 
 /**
- * Hexadecimal delimiters.
+ * To Hexadecimal delimiters.
  */
-export const HEX_DELIM_OPTIONS = ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "0x", "\\x", "None"];
+export const TO_HEX_DELIM_OPTIONS = ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "0x", "\\x", "None"];
+
+
+/**
+ * From Hexadecimal delimiters.
+ */
+export const FROM_HEX_DELIM_OPTIONS = ["Auto"].concat(TO_HEX_DELIM_OPTIONS);

+ 2 - 2
src/core/operations/FromHex.mjs

@@ -5,7 +5,7 @@
  */
 
 import Operation from "../Operation";
-import {fromHex, HEX_DELIM_OPTIONS} from "../lib/Hex";
+import {fromHex, FROM_HEX_DELIM_OPTIONS} from "../lib/Hex";
 import Utils from "../Utils";
 
 /**
@@ -28,7 +28,7 @@ class FromHex extends Operation {
             {
                 name: "Delimiter",
                 type: "option",
-                value: HEX_DELIM_OPTIONS
+                value: FROM_HEX_DELIM_OPTIONS
             }
         ];
     }

+ 2 - 2
src/core/operations/ToHex.mjs

@@ -5,7 +5,7 @@
  */
 
 import Operation from "../Operation";
-import {toHex, HEX_DELIM_OPTIONS} from "../lib/Hex";
+import {toHex, TO_HEX_DELIM_OPTIONS} from "../lib/Hex";
 import Utils from "../Utils";
 
 /**
@@ -28,7 +28,7 @@ class ToHex extends Operation {
             {
                 name: "Delimiter",
                 type: "option",
-                value: HEX_DELIM_OPTIONS
+                value: TO_HEX_DELIM_OPTIONS
             }
         ];
     }

+ 1 - 1
src/core/operations/legacy/BSON.js

@@ -29,7 +29,7 @@ const BSON = {
             const data = JSON.parse(input);
             return bson.serialize(data).buffer;
         } catch (err) {
-            return err.toString();
+            throw err.toString();
         }
     },
 

+ 10 - 0
src/core/operations/legacy/ByteRepr.js

@@ -18,6 +18,16 @@ const ByteRepr = {
      * @default
      */
     DELIM_OPTIONS: ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF"],
+    /**
+     * @constant
+     * @default
+     */
+    TO_HEX_DELIM_OPTIONS: ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "0x", "\\x", "None"],
+    /**
+     * @constant
+     * @default
+     */
+    FROM_HEX_DELIM_OPTIONS: ["Auto", "Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "0x", "\\x", "None"],
     /**
      * @constant
      * @default

+ 2 - 2
src/core/operations/legacy/Checksum.js

@@ -120,7 +120,7 @@ const Checksum = {
     /**
      * CRC-32 Checksum operation.
      *
-     * @param {string} input
+     * @param {ArrayBuffer} input
      * @param {Object[]} args
      * @returns {string}
      */
@@ -132,7 +132,7 @@ const Checksum = {
     /**
      * CRC-16 Checksum operation.
      *
-     * @param {string} input
+     * @param {ArrayBuffer} input
      * @param {Object[]} args
      * @returns {string}
      */

+ 364 - 0
src/core/operations/legacy/PGP.js

@@ -0,0 +1,364 @@
+import * as kbpgp from "kbpgp";
+import {promisify} from "es6-promisify";
+
+
+/**
+ * PGP operations.
+ *
+ * @author tlwr [toby@toby.codes]
+ * @author Matt C [matt@artemisbot.uk]
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ *
+ * @namespace
+ */
+const PGP = {
+
+    /**
+     * @constant
+     * @default
+     */
+    KEY_TYPES: ["RSA-1024", "RSA-2048", "RSA-4096", "ECC-256", "ECC-384"],
+
+
+    /**
+     * Get size of subkey
+     *
+     * @private
+     * @param {number} keySize
+     * @returns {number}
+     */
+    _getSubkeySize(keySize) {
+        return {
+            1024: 1024,
+            2048: 1024,
+            4096: 2048,
+            256:   256,
+            384:   256,
+        }[keySize];
+    },
+
+
+    /**
+     * Progress callback
+     *
+     * @private
+     */
+    _ASP: new kbpgp.ASP({
+        "progress_hook": info => {
+            let msg = "";
+
+            switch (info.what) {
+                case "guess":
+                    msg = "Guessing a prime";
+                    break;
+                case "fermat":
+                    msg = "Factoring prime using Fermat's factorization method";
+                    break;
+                case "mr":
+                    msg = "Performing Miller-Rabin primality test";
+                    break;
+                case "passed_mr":
+                    msg = "Passed Miller-Rabin primality test";
+                    break;
+                case "failed_mr":
+                    msg = "Failed Miller-Rabin primality test";
+                    break;
+                case "found":
+                    msg = "Prime found";
+                    break;
+                default:
+                    msg = `Stage: ${info.what}`;
+            }
+
+            if (ENVIRONMENT_IS_WORKER())
+                self.sendStatusMessage(msg);
+        }
+    }),
+
+
+    /**
+     * Import private key and unlock if necessary
+     *
+     * @private
+     * @param {string} privateKey
+     * @param {string} [passphrase]
+     * @returns {Object}
+     */
+    async _importPrivateKey(privateKey, passphrase) {
+        try {
+            const key = await promisify(kbpgp.KeyManager.import_from_armored_pgp)({
+                armored: privateKey,
+                opts: {
+                    "no_check_keys": true
+                }
+            });
+            if (key.is_pgp_locked()) {
+                if (passphrase) {
+                    await promisify(key.unlock_pgp.bind(key))({
+                        passphrase
+                    });
+                } else {
+                    throw "Did not provide passphrase with locked private key.";
+                }
+            }
+            return key;
+        } catch (err) {
+            throw `Could not import private key: ${err}`;
+        }
+    },
+
+
+    /**
+     * Import public key
+     *
+     * @private
+     * @param {string} publicKey
+     * @returns {Object}
+     */
+    async _importPublicKey (publicKey) {
+        try {
+            const key = await promisify(kbpgp.KeyManager.import_from_armored_pgp)({
+                armored: publicKey,
+                opts: {
+                    "no_check_keys": true
+                }
+            });
+            return key;
+        } catch (err) {
+            throw `Could not import public key: ${err}`;
+        }
+    },
+
+
+    /**
+     * Generate PGP Key Pair operation.
+     *
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    runGenerateKeyPair(input, args) {
+        let [keyType, keySize] = args[0].split("-"),
+            password = args[1],
+            name = args[2],
+            email = args[3],
+            userIdentifier = "";
+
+        if (name) userIdentifier += name;
+        if (email) userIdentifier += ` <${email}>`;
+
+        let flags = kbpgp.const.openpgp.certify_keys;
+        flags |= kbpgp.const.openpgp.sign_data;
+        flags |= kbpgp.const.openpgp.auth;
+        flags |= kbpgp.const.openpgp.encrypt_comm;
+        flags |= kbpgp.const.openpgp.encrypt_storage;
+
+        let keyGenerationOptions = {
+            userid: userIdentifier,
+            ecc: keyType === "ecc",
+            primary: {
+                "nbits": keySize,
+                "flags": flags,
+                "expire_in": 0
+            },
+            subkeys: [{
+                "nbits": PGP._getSubkeySize(keySize),
+                "flags": kbpgp.const.openpgp.sign_data,
+                "expire_in": 86400 * 365 * 8
+            }, {
+                "nbits": PGP._getSubkeySize(keySize),
+                "flags": kbpgp.const.openpgp.encrypt_comm | kbpgp.const.openpgp.encrypt_storage,
+                "expire_in": 86400 * 365 * 2
+            }],
+            asp: PGP._ASP
+        };
+
+        return new Promise(async (resolve, reject) => {
+            try {
+                const unsignedKey = await promisify(kbpgp.KeyManager.generate)(keyGenerationOptions);
+                await promisify(unsignedKey.sign.bind(unsignedKey))({});
+                let signedKey = unsignedKey;
+                let privateKeyExportOptions = {};
+                if (password) privateKeyExportOptions.passphrase = password;
+                const privateKey = await promisify(signedKey.export_pgp_private.bind(signedKey))(privateKeyExportOptions);
+                const publicKey = await promisify(signedKey.export_pgp_public.bind(signedKey))({});
+                resolve(privateKey + "\n" + publicKey.trim());
+            } catch (err) {
+                reject(`Error whilst generating key pair: ${err}`);
+            }
+        });
+    },
+
+
+    /**
+     * PGP Encrypt operation.
+     *
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    async runEncrypt(input, args) {
+        let plaintextMessage = input,
+            plainPubKey = args[0],
+            key,
+            encryptedMessage;
+
+        if (!plainPubKey) return "Enter the public key of the recipient.";
+
+        try {
+            key = await promisify(kbpgp.KeyManager.import_from_armored_pgp)({
+                armored: plainPubKey,
+            });
+        } catch (err) {
+            throw `Could not import public key: ${err}`;
+        }
+
+        try {
+            encryptedMessage = await promisify(kbpgp.box)({
+                "msg": plaintextMessage,
+                "encrypt_for": key,
+                "asp": PGP._ASP
+            });
+        } catch (err) {
+            throw `Couldn't encrypt message with provided public key: ${err}`;
+        }
+
+        return encryptedMessage.toString();
+    },
+
+
+    /**
+     * PGP Decrypt operation.
+     *
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    async runDecrypt(input, args) {
+        let encryptedMessage = input,
+            privateKey = args[0],
+            passphrase = args[1],
+            keyring = new kbpgp.keyring.KeyRing(),
+            plaintextMessage;
+
+        if (!privateKey) return "Enter the private key of the recipient.";
+
+        const key = await PGP._importPrivateKey(privateKey, passphrase);
+        keyring.add_key_manager(key);
+
+        try {
+            plaintextMessage = await promisify(kbpgp.unbox)({
+                armored: encryptedMessage,
+                keyfetch: keyring,
+                asp: PGP._ASP
+            });
+        } catch (err) {
+            throw `Couldn't decrypt message with provided private key: ${err}`;
+        }
+
+        return plaintextMessage.toString();
+    },
+
+
+    /**
+     * PGP Sign Message operation.
+     *
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    async runSign(input, args) {
+        let message = input,
+            privateKey = args[0],
+            passphrase = args[1],
+            publicKey = args[2],
+            signedMessage;
+
+        if (!privateKey) return "Enter the private key of the signer.";
+        if (!publicKey) return "Enter the public key of the recipient.";
+        const privKey = await PGP._importPrivateKey(privateKey, passphrase);
+        const pubKey = await PGP._importPublicKey(publicKey);
+
+        try {
+            signedMessage = await promisify(kbpgp.box)({
+                "msg": message,
+                "encrypt_for": pubKey,
+                "sign_with": privKey,
+                "asp": PGP._ASP
+            });
+        } catch (err) {
+            throw `Couldn't sign message: ${err}`;
+        }
+
+        return signedMessage;
+    },
+
+
+    /**
+     * PGP Verify Message operation.
+     *
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    async runVerify(input, args) {
+        let signedMessage = input,
+            publicKey = args[0],
+            privateKey = args[1],
+            passphrase = args[2],
+            keyring = new kbpgp.keyring.KeyRing(),
+            unboxedLiterals;
+
+        if (!publicKey) return "Enter the public key of the signer.";
+        if (!privateKey) return "Enter the private key of the recipient.";
+        const privKey = await PGP._importPrivateKey(privateKey, passphrase);
+        const pubKey = await PGP._importPublicKey(publicKey);
+        keyring.add_key_manager(privKey);
+        keyring.add_key_manager(pubKey);
+
+        try {
+            unboxedLiterals = await promisify(kbpgp.unbox)({
+                armored: signedMessage,
+                keyfetch: keyring,
+                asp: PGP._ASP
+            });
+            const ds = unboxedLiterals[0].get_data_signer();
+            if (ds) {
+                const km = ds.get_key_manager();
+                if (km) {
+                    const signer = km.get_userids_mark_primary()[0].components;
+                    let text = "Signed by ";
+                    if (signer.email || signer.username || signer.comment) {
+                        if (signer.username) {
+                            text += `${signer.username} `;
+                        }
+                        if (signer.comment) {
+                            text += `${signer.comment} `;
+                        }
+                        if (signer.email) {
+                            text += `<${signer.email}>`;
+                        }
+                        text += "\n";
+                    }
+                    text += [
+                        `PGP fingerprint: ${km.get_pgp_fingerprint().toString("hex")}`,
+                        `Signed on ${new Date(ds.sig.hashed_subpackets[0].time * 1000).toUTCString()}`,
+                        "----------------------------------\n"
+                    ].join("\n");
+                    text += unboxedLiterals.toString();
+                    return text.trim();
+                } else {
+                    return "Could not identify a key manager.";
+                }
+            } else {
+                return "The data does not appear to be signed.";
+            }
+        } catch (err) {
+            return `Couldn't verify message: ${err}`;
+        }
+    },
+};
+
+export default PGP;

+ 164 - 0
src/core/operations/legacy/ToTable.js

@@ -0,0 +1,164 @@
+/**
+ * ToTable operations.
+ *
+ * @author Mark Jones [github.com/justanothermark]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ *
+ * @namespace
+ */
+import Utils from "../Utils.js";
+
+const ToTable = {
+
+    /**
+     * @constant
+     * @default
+     */
+    FORMATS: [
+        "ASCII",
+        "HTML"
+    ],
+
+
+    /**
+     * To Table operation.
+     *
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {html}
+     */
+    runToTable: function (input, args) {
+        const [cellDelims, rowDelims, firstRowHeader, format] = args;
+
+        // Process the input into a nested array of elements.
+        const tableData = Utils.parseCSV(input, cellDelims.split(""), rowDelims.split(""));
+
+        if (!tableData.length) return "";
+
+        // Render the data in the requested format.
+        switch (format) {
+            case "ASCII":
+                return asciiOutput(tableData);
+            case "HTML":
+            default:
+                return htmlOutput(tableData);
+        }
+
+        /**
+         * Outputs an array of data as an ASCII table.
+         *
+         * @param {string[][]} tableData
+         * @returns {string}
+         */
+        function asciiOutput(tableData) {
+            const horizontalBorder = "-";
+            const verticalBorder = "|";
+            const crossBorder = "+";
+
+            let output = "";
+            let longestCells = [];
+
+            // Find longestCells value per column to pad cells equally.
+            tableData.forEach(function(row, index) {
+                row.forEach(function(cell, cellIndex) {
+                    if (longestCells[cellIndex] === undefined || cell.length > longestCells[cellIndex]) {
+                        longestCells[cellIndex] = cell.length;
+                    }
+                });
+            });
+
+            // Add the top border of the table to the output.
+            output += outputHorizontalBorder(longestCells);
+
+            // If the first row is a header, remove the row from the data and
+            // add it to the output with another horizontal border.
+            if (firstRowHeader) {
+                let row = tableData.shift();
+                output += outputRow(row, longestCells);
+                output += outputHorizontalBorder(longestCells);
+            }
+
+            // Add the rest of the table rows.
+            tableData.forEach(function(row, index) {
+                output += outputRow(row, longestCells);
+            });
+
+            // Close the table with a final horizontal border.
+            output += outputHorizontalBorder(longestCells);
+
+            return output;
+
+            /**
+             * Outputs a row of correctly padded cells.
+             */
+            function outputRow(row, longestCells) {
+                let rowOutput = verticalBorder;
+                row.forEach(function(cell, index) {
+                    rowOutput += " " + cell + " ".repeat(longestCells[index] - cell.length) + " " + verticalBorder;
+                });
+                rowOutput += "\n";
+                return rowOutput;
+            }
+
+            /**
+             * Outputs a horizontal border with a different character where
+             * the horizontal border meets a vertical border.
+             */
+            function outputHorizontalBorder(longestCells) {
+                let rowOutput = crossBorder;
+                longestCells.forEach(function(cellLength) {
+                    rowOutput += horizontalBorder.repeat(cellLength + 2) + crossBorder;
+                });
+                rowOutput += "\n";
+                return rowOutput;
+            }
+        }
+
+        /**
+         * Outputs a table of data as a HTML table.
+         *
+         * @param {string[][]} tableData
+         * @returns {string}
+         */
+        function htmlOutput(tableData) {
+            // Start the HTML output with suitable classes for styling.
+            let output = "<table class='table table-hover table-condensed table-bordered table-nonfluid'>";
+
+            // If the first row is a header then put it in <thead> with <th> cells.
+            if (firstRowHeader) {
+                let row = tableData.shift();
+                output += "<thead>";
+                output += outputRow(row, "th");
+                output += "</thead>";
+            }
+
+            // Output the rest of the rows in the <tbody>.
+            output += "<tbody>";
+            tableData.forEach(function(row, index) {
+                output += outputRow(row, "td");
+            });
+
+            // Close the body and table elements.
+            output += "</tbody></table>";
+            return output;
+
+          /**
+           * Outputs a table row.
+           *
+           * @param {string[]} row
+           * @param {string} cellType
+           */
+            function outputRow(row, cellType) {
+                let output = "<tr>";
+                row.forEach(function(cell) {
+                    output += "<" + cellType + ">" + cell + "</" + cellType + ">";
+                });
+                output += "</tr>";
+                return output;
+            }
+        }
+    }
+};
+
+export default ToTable;

+ 3 - 0
src/web/App.js

@@ -83,6 +83,9 @@ App.prototype.loaded = function() {
     // Clear the loading message interval
     clearInterval(window.loadingMsgsInt);
 
+    // Remove the loading error handler
+    window.removeEventListener("error", window.loadingErrorHandler);
+
     document.dispatchEvent(this.manager.apploaded);
 };
 

+ 46 - 5
src/web/html/index.html

@@ -42,7 +42,7 @@
             }
 
             // Define loading messages
-            const loadingMsgs = [
+            var loadingMsgs = [
                 "Proving P = NP...",
                 "Computing 6 x 9...",
                 "Mining bitcoin...",
@@ -66,18 +66,18 @@
 
             // Shuffle array using Durstenfeld algorithm
             for (let i = loadingMsgs.length - 1; i > 0; --i) {
-                const j = Math.floor(Math.random() * (i + 1));
-                const temp = loadingMsgs[i];
+                var j = Math.floor(Math.random() * (i + 1));
+                var temp = loadingMsgs[i];
                 loadingMsgs[i] = loadingMsgs[j];
                 loadingMsgs[j] = temp;
             }
 
             // Show next loading message and move it to the end of the array
             function changeLoadingMsg() {
-                const msg = loadingMsgs.shift();
+                var msg = loadingMsgs.shift();
                 loadingMsgs.push(msg);
                 try {
-                    const el = document.getElementById("preloader-msg");
+                    var el = document.getElementById("preloader-msg");
                     if (!el.classList.contains("loading"))
                         el.classList.add("loading"); // Causes CSS transition on first message
                     el.innerHTML = msg;
@@ -86,6 +86,46 @@
 
             changeLoadingMsg();
             window.loadingMsgsInt = setInterval(changeLoadingMsg, (Math.random() * 2000) + 1500);
+
+            // If any errors are thrown during loading, handle them here
+            function loadingErrorHandler(e) {
+                function escapeHtml(str) {
+                    var HTML_CHARS = {
+                        "&": "&amp;",
+                        "<": "&lt;",
+                        ">": "&gt;",
+                        '"': "&quot;",
+                        "'": "&#x27;", // &apos; not recommended because it's not in the HTML spec
+                        "/": "&#x2F;", // forward slash is included as it helps end an HTML entity
+                        "`": "&#x60;"
+                    };
+
+                    return str.replace(/[&<>"'/`]/g, function (match) {
+                        return HTML_CHARS[match];
+                    });
+                }
+
+                var msg = e.message +
+                    (e.filename ? "\nFilename: " + e.filename : "") +
+                    (e.lineno ? "\nLine: " + e.lineno : "") +
+                    (e.colno ? "\nColumn: " + e.colno : "") +
+                    (e.error ? "\nError: " + e.error : "") +
+                    "\nUser-Agent: " + navigator.userAgent +
+                    "\nCyberChef version: <%= htmlWebpackPlugin.options.version %>";
+
+                clearInterval(window.loadingMsgsInt);
+                document.getElementById("preloader").remove();
+                document.getElementById("preloader-msg").remove();
+                document.getElementById("preloader-error").innerHTML =
+                    "CyberChef encountered an error while loading.<br><br>" +
+                    "The following browser versions are supported:" +
+                    "<ul><li>Google Chrome 40+</li><li>Mozilla Firefox 35+</li><li>Microsoft Edge 14+</li></ul>" +
+                    "Your user agent is:<br>" + escapeHtml(navigator.userAgent) + "<br><br>" +
+                    "If your browser is supported, please <a href='https://github.com/gchq/CyberChef/issues/new'>" +
+                    "raise an issue</a> including the following details:<br><br>" +
+                    "<pre>" + escapeHtml(msg) + "</pre>";
+            };
+            window.addEventListener("error", loadingErrorHandler);
         </script>
         <% if (htmlWebpackPlugin.options.inline) { %>
             <meta name="robots" content="noindex" />
@@ -100,6 +140,7 @@
         <div id="loader-wrapper">
             <div id="preloader" class="loader"></div>
             <div id="preloader-msg" class="loading-msg"></div>
+            <div id="preloader-error" class="loading-error"></div>
         </div>
         <!-- End preloader overlay -->
         <span id="edit-favourites" class="btn btn-default btn-sm"><img aria-hidden="true" src="<%- require('../static/images/favourite-16x16.png') %>" alt="Star Icon"/> Edit</span>

+ 1 - 0
src/web/index.js

@@ -64,3 +64,4 @@ window.compileMessage = COMPILE_MSG;
 window.CanvasComponents = CanvasComponents;
 
 document.addEventListener("DOMContentLoaded", main, false);
+

+ 8 - 0
src/web/stylesheets/preloader.css

@@ -74,6 +74,14 @@
     transition: all 0.1s ease-in;
 }
 
+.loading-error {
+    display: block;
+    position: relative;
+    width: 600px;
+    left: calc(50% - 300px);
+    top: 10%;
+}
+
 
 /* Loaded */
 .loaded .loading-msg {

+ 2 - 1
test/index.mjs

@@ -31,6 +31,7 @@ import "./tests/operations/Base64";
 // import "./tests/operations/BSON.js";
 // import "./tests/operations/ByteRepr.js";
 // import "./tests/operations/CharEnc.js";
+//import "./tests/operations/Checksum.js";
 // import "./tests/operations/Cipher.js";
 // import "./tests/operations/Code.js";
 // import "./tests/operations/Compress.js";
@@ -45,7 +46,7 @@ import "./tests/operations/Base64";
 // import "./tests/operations/NetBIOS.js";
 // import "./tests/operations/OTP.js";
 // import "./tests/operations/Regex.js";
-import "./tests/operations/Rotate.mjs";
+import "./tests/operations/Rotate";
 // import "./tests/operations/StrUtils.js";
 // import "./tests/operations/SeqUtils.js";
 import "./tests/operations/SetUnion";

+ 120 - 0
test/tests/operations/Checksum.js

@@ -0,0 +1,120 @@
+/**
+ * Checksum tests.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+import TestRegister from "../../TestRegister.js";
+
+const BASIC_STRING = "The ships hung in the sky in much the same way that bricks don't.";
+const UTF8_STR = "ნუ პანიკას";
+const ALL_BYTES = [
+    "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
+    "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
+    "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f",
+    "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f",
+    "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f",
+    "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f",
+    "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f",
+    "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f",
+    "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f",
+    "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f",
+    "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf",
+    "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf",
+    "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf",
+    "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf",
+    "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef",
+    "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff",
+].join("");
+
+TestRegister.addTests([
+    {
+        name: "CRC-16: nothing",
+        input: "",
+        expectedOutput: "0000",
+        recipeConfig: [
+            {
+                "op": "CRC-16 Checksum",
+                "args": []
+            }
+        ]
+    },
+    {
+        name: "CRC-16: basic string",
+        input: BASIC_STRING,
+        expectedOutput: "0c70",
+        recipeConfig: [
+            {
+                "op": "CRC-16 Checksum",
+                "args": []
+            }
+        ]
+    },
+    {
+        name: "CRC-16: UTF-8",
+        input: UTF8_STR,
+        expectedOutput: "dcf6",
+        recipeConfig: [
+            {
+                "op": "CRC-16 Checksum",
+                "args": []
+            }
+        ]
+    },
+    {
+        name: "CRC-16: all bytes",
+        input: ALL_BYTES,
+        expectedOutput: "bad3",
+        recipeConfig: [
+            {
+                "op": "CRC-16 Checksum",
+                "args": []
+            }
+        ]
+    },
+    {
+        name: "CRC-32: nothing",
+        input: "",
+        expectedOutput: "00000000",
+        recipeConfig: [
+            {
+                "op": "CRC-32 Checksum",
+                "args": []
+            }
+        ]
+    },
+    {
+        name: "CRC-32: basic string",
+        input: BASIC_STRING,
+        expectedOutput: "bf4b739c",
+        recipeConfig: [
+            {
+                "op": "CRC-32 Checksum",
+                "args": []
+            }
+        ]
+    },
+    {
+        name: "CRC-32: UTF-8",
+        input: UTF8_STR,
+        expectedOutput: "87553290",
+        recipeConfig: [
+            {
+                "op": "CRC-32 Checksum",
+                "args": []
+            }
+        ]
+    },
+    {
+        name: "CRC-32: all bytes",
+        input: ALL_BYTES,
+        expectedOutput: "29058c73",
+        recipeConfig: [
+            {
+                "op": "CRC-32 Checksum",
+                "args": []
+            }
+        ]
+    },
+]);

+ 2 - 2
webpack.config.js

@@ -66,7 +66,7 @@ module.exports = {
         rules: [
             {
                 test: /\.m?js$/,
-                exclude: /node_modules/,
+                exclude: /node_modules\/(?!jsesc)/,
                 loader: "babel-loader?compact=false"
             },
             {
@@ -118,7 +118,7 @@ module.exports = {
         chunks: false,
         modules: false,
         entrypoints: false,
-        warningsFilter: /source-map/,
+        warningsFilter: [/source-map/, /dependency is an expression/],
     },
     node: {
         fs: "empty"

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません