diff --git a/src/core/lib/Extract.mjs b/src/core/lib/Extract.mjs new file mode 100644 index 00000000..ba57d758 --- /dev/null +++ b/src/core/lib/Extract.mjs @@ -0,0 +1,41 @@ +/** + * Identifier extraction functions + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + */ + +/** + * Runs search operations across the input data using regular expressions. + * + * @param {string} input + * @param {RegExp} searchRegex + * @param {RegExp} removeRegex - A regular expression defining results to remove from the + * final list + * @param {boolean} includeTotal - Whether or not to include the total number of results + * @returns {string} + */ +export function search (input, searchRegex, removeRegex, includeTotal) { + let output = "", + total = 0, + match; + + while ((match = searchRegex.exec(input))) { + // Moves pointer when an empty string is matched (prevents infinite loop) + if (match.index === searchRegex.lastIndex) { + searchRegex.lastIndex++; + } + + if (removeRegex && removeRegex.test(match[0])) + continue; + total++; + output += match[0] + "\n"; + } + + if (includeTotal) + output = "Total found: " + total + "\n\n" + output; + + return output; +} diff --git a/src/core/lib/PGP.mjs b/src/core/lib/PGP.mjs new file mode 100644 index 00000000..ea222ae8 --- /dev/null +++ b/src/core/lib/PGP.mjs @@ -0,0 +1,116 @@ +/** + * PGP functions. + * + * @author tlwr [toby@toby.codes] + * @author Matt C [matt@artemisbot.uk] + * @author n1474335 [n1474335@gmail.com] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + * + */ + +import kbpgp from "kbpgp"; +import promisifyDefault from "es6-promisify"; +const promisify = promisifyDefault.promisify; +/** + * Progress callback + * + */ +export const ASP = 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); + } +}); + +/** + * Get size of subkey + * + * @param {number} keySize + * @returns {number} + */ +export function getSubkeySize(keySize) { + return { + 1024: 1024, + 2048: 1024, + 4096: 2048, + 256: 256, + 384: 256, + }[keySize]; +} + +/** +* Import private key and unlock if necessary +* +* @param {string} privateKey +* @param {string} [passphrase] +* @returns {Object} +*/ +export async function 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 + * + * @param {string} publicKey + * @returns {Object} + */ +export async function 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}`; + } +} diff --git a/src/core/operations/AESDecrypt.mjs b/src/core/operations/AESDecrypt.mjs index 01415ddc..7d9ac5ca 100644 --- a/src/core/operations/AESDecrypt.mjs +++ b/src/core/operations/AESDecrypt.mjs @@ -7,6 +7,7 @@ import Operation from "../Operation"; import Utils from "../Utils"; import forge from "node-forge/dist/forge.min.js"; +import OperationError from "../errors/OperationError"; /** * AES Decrypt operation @@ -65,6 +66,8 @@ class AESDecrypt extends Operation { * @param {string} input * @param {Object[]} args * @returns {string} + * + * @throws {OperationError} if cannot decrypt input or invalid key length */ run(input, args) { const key = Utils.convertToByteArray(args[0].string, args[0].option), @@ -75,12 +78,12 @@ class AESDecrypt extends Operation { gcmTag = Utils.convertToByteString(args[5].string, args[5].option); if ([16, 24, 32].indexOf(key.length) < 0) { - return `Invalid key length: ${key.length} bytes + throw new OperationError(`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`; + 32 bytes = AES-256`); } input = Utils.convertToByteString(input, inputType); @@ -96,7 +99,7 @@ The following algorithms will be used based on the size of the key: if (result) { return outputType === "Hex" ? decipher.output.toHex() : decipher.output.getBytes(); } else { - return "Unable to decrypt input with these parameters."; + throw new OperationError("Unable to decrypt input with these parameters."); } } diff --git a/src/core/operations/AESEncrypt.mjs b/src/core/operations/AESEncrypt.mjs index 62763c5f..98a4c259 100644 --- a/src/core/operations/AESEncrypt.mjs +++ b/src/core/operations/AESEncrypt.mjs @@ -7,6 +7,7 @@ import Operation from "../Operation"; import Utils from "../Utils"; import forge from "node-forge/dist/forge.min.js"; +import OperationError from "../errors/OperationError"; /** * AES Encrypt operation @@ -59,6 +60,8 @@ class AESEncrypt extends Operation { * @param {string} input * @param {Object[]} args * @returns {string} + * + * @throws {OperationError} if invalid key length */ run(input, args) { const key = Utils.convertToByteArray(args[0].string, args[0].option), @@ -68,12 +71,12 @@ class AESEncrypt extends Operation { outputType = args[4]; if ([16, 24, 32].indexOf(key.length) < 0) { - return `Invalid key length: ${key.length} bytes + throw new OperationError(`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`; + 32 bytes = AES-256`); } input = Utils.convertToByteString(input, inputType); diff --git a/src/core/operations/AffineCipherDecode.mjs b/src/core/operations/AffineCipherDecode.mjs index 184fa67c..3f65c150 100644 --- a/src/core/operations/AffineCipherDecode.mjs +++ b/src/core/operations/AffineCipherDecode.mjs @@ -42,6 +42,8 @@ class AffineCipherDecode extends Operation { * @param {string} input * @param {Object[]} args * @returns {string} + * + * @throws {OperationError} if a or b values are invalid */ run(input, args) { const alphabet = "abcdefghijklmnopqrstuvwxyz", diff --git a/src/core/operations/BifidCipherDecode.mjs b/src/core/operations/BifidCipherDecode.mjs index dbfc7628..f5e2ad47 100644 --- a/src/core/operations/BifidCipherDecode.mjs +++ b/src/core/operations/BifidCipherDecode.mjs @@ -37,6 +37,8 @@ class BifidCipherDecode extends Operation { * @param {string} input * @param {Object[]} args * @returns {string} + * + * @throws {OperationError} if invalid key */ run(input, args) { const keywordStr = args[0].toUpperCase().replace("J", "I"), diff --git a/src/core/operations/BifidCipherEncode.mjs b/src/core/operations/BifidCipherEncode.mjs index 11050349..41c29db0 100644 --- a/src/core/operations/BifidCipherEncode.mjs +++ b/src/core/operations/BifidCipherEncode.mjs @@ -37,6 +37,8 @@ class BifidCipherEncode extends Operation { * @param {string} input * @param {Object[]} args * @returns {string} + * + * @throws {OperationError} if key is invalid */ run(input, args) { const keywordStr = args[0].toUpperCase().replace("J", "I"), diff --git a/src/core/operations/CartesianProduct.mjs b/src/core/operations/CartesianProduct.mjs index 5125511b..0d5346fb 100644 --- a/src/core/operations/CartesianProduct.mjs +++ b/src/core/operations/CartesianProduct.mjs @@ -41,7 +41,7 @@ class CartesianProduct extends Operation { * Validate input length * * @param {Object[]} sets - * @throws {Error} if fewer than 2 sets + * @throws {OperationError} if fewer than 2 sets */ validateSampleNumbers(sets) { if (!sets || sets.length < 2) { diff --git a/src/core/operations/DisassembleX86.mjs b/src/core/operations/DisassembleX86.mjs new file mode 100644 index 00000000..602f281c --- /dev/null +++ b/src/core/operations/DisassembleX86.mjs @@ -0,0 +1,131 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import * as disassemble from "../vendor/DisassembleX86-64"; +import OperationError from "../errors/OperationError"; + +/** + * Disassemble x86 operation + */ +class DisassembleX86 extends Operation { + + /** + * DisassembleX86 constructor + */ + constructor() { + super(); + + this.name = "Disassemble x86"; + this.module = "Shellcode"; + this.description = "Disassembly is the process of translating machine language into assembly language.

This operation supports 64-bit, 32-bit and 16-bit code written for Intel or AMD x86 processors. It is particularly useful for reverse engineering shellcode.

Input should be in hexadecimal."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Bit mode", + "type": "option", + "value": ["64", "32", "16"] + }, + { + "name": "Compatibility", + "type": "option", + "value": [ + "Full x86 architecture", + "Knights Corner", + "Larrabee", + "Cyrix", + "Geode", + "Centaur", + "X86/486" + ] + }, + { + "name": "Code Segment (CS)", + "type": "number", + "value": 16 + }, + { + "name": "Offset (IP)", + "type": "number", + "value": 0 + }, + { + "name": "Show instruction hex", + "type": "boolean", + "value": true + }, + { + "name": "Show instruction position", + "type": "boolean", + "value": true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if invalid mode value + */ + run(input, args) { + const mode = args[0], + compatibility = args[1], + codeSegment = args[2], + offset = args[3], + showInstructionHex = args[4], + showInstructionPos = args[5]; + + switch (mode) { + case "64": + disassemble.setBitMode(2); + break; + case "32": + disassemble.setBitMode(1); + break; + case "16": + disassemble.setBitMode(0); + break; + default: + throw new OperationError("Invalid mode value"); + } + + switch (compatibility) { + case "Full x86 architecture": + disassemble.CompatibilityMode(0); + break; + case "Knights Corner": + disassemble.CompatibilityMode(1); + break; + case "Larrabee": + disassemble.CompatibilityMode(2); + break; + case "Cyrix": + disassemble.CompatibilityMode(3); + break; + case "Geode": + disassemble.CompatibilityMode(4); + break; + case "Centaur": + disassemble.CompatibilityMode(5); + break; + case "X86/486": + disassemble.CompatibilityMode(6); + break; + } + + disassemble.SetBasePosition(codeSegment + ":" + offset); + disassemble.setShowInstructionHex(showInstructionHex); + disassemble.setShowInstructionPos(showInstructionPos); + disassemble.LoadBinCode(input.replace(/\s/g, "")); + return disassemble.LDisassemble(); + } + +} + +export default DisassembleX86; diff --git a/src/core/operations/DropBytes.mjs b/src/core/operations/DropBytes.mjs index f62ac1bf..e697f4e9 100644 --- a/src/core/operations/DropBytes.mjs +++ b/src/core/operations/DropBytes.mjs @@ -5,6 +5,7 @@ */ import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; /** * Drop bytes operation @@ -45,6 +46,8 @@ class DropBytes extends Operation { * @param {ArrayBuffer} input * @param {Object[]} args * @returns {ArrayBuffer} + * + * @throws {OperationError} if invalid input */ run(input, args) { const start = args[0], @@ -52,7 +55,7 @@ class DropBytes extends Operation { applyToEachLine = args[2]; if (start < 0 || length < 0) - throw "Error: Invalid value"; + throw new OperationError("Error: Invalid value"); if (!applyToEachLine) { const left = input.slice(0, start), diff --git a/src/core/operations/ExtractDates.mjs b/src/core/operations/ExtractDates.mjs new file mode 100644 index 00000000..530db194 --- /dev/null +++ b/src/core/operations/ExtractDates.mjs @@ -0,0 +1,52 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { search } from "../lib/Extract"; + +/** + * Extract dates operation + */ +class ExtractDates extends Operation { + + /** + * ExtractDates constructor + */ + constructor() { + super(); + + this.name = "Extract dates"; + this.module = "Regex"; + this.description = "Extracts dates in the following formatsDividers can be any of /, -, . or space"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Display total", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const displayTotal = args[0], + date1 = "(?:19|20)\\d\\d[- /.](?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])", // yyyy-mm-dd + date2 = "(?:0[1-9]|[12][0-9]|3[01])[- /.](?:0[1-9]|1[012])[- /.](?:19|20)\\d\\d", // dd/mm/yyyy + date3 = "(?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])[- /.](?:19|20)\\d\\d", // mm/dd/yyyy + regex = new RegExp(date1 + "|" + date2 + "|" + date3, "ig"); + + return search(input, regex, null, displayTotal); + } + +} + +export default ExtractDates; diff --git a/src/core/operations/ExtractDomains.mjs b/src/core/operations/ExtractDomains.mjs new file mode 100644 index 00000000..8eae8064 --- /dev/null +++ b/src/core/operations/ExtractDomains.mjs @@ -0,0 +1,49 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { search } from "../lib/Extract"; + +/** + * Extract domains operation + */ +class ExtractDomains extends Operation { + + /** + * ExtractDomains constructor + */ + constructor() { + super(); + + this.name = "Extract domains"; + this.module = "Regex"; + this.description = "Extracts domain names.
Note that this will not include paths. Use Extract URLs to find entire URLs."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Display total", + "type": "boolean", + "value": "Extract.DISPLAY_TOTAL" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const displayTotal = args[0], + regex = /\b((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b/ig; + + return search(input, regex, null, displayTotal); + } + +} + +export default ExtractDomains; diff --git a/src/core/operations/ExtractEmailAddresses.mjs b/src/core/operations/ExtractEmailAddresses.mjs new file mode 100644 index 00000000..6c2dc740 --- /dev/null +++ b/src/core/operations/ExtractEmailAddresses.mjs @@ -0,0 +1,49 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { search } from "../lib/Extract"; + +/** + * Extract email addresses operation + */ +class ExtractEmailAddresses extends Operation { + + /** + * ExtractEmailAddresses constructor + */ + constructor() { + super(); + + this.name = "Extract email addresses"; + this.module = "Regex"; + this.description = "Extracts all email addresses from the input."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Display total", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const displayTotal = args[0], + regex = /\b\w[-.\w]*@[-\w]+(?:\.[-\w]+)*\.[A-Z]{2,4}\b/ig; + + return search(input, regex, null, displayTotal); + } + +} + +export default ExtractEmailAddresses; diff --git a/src/core/operations/ExtractFilePaths.mjs b/src/core/operations/ExtractFilePaths.mjs new file mode 100644 index 00000000..11f10f72 --- /dev/null +++ b/src/core/operations/ExtractFilePaths.mjs @@ -0,0 +1,79 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { search } from "../lib/Extract"; +/** + * Extract file paths operation + */ +class ExtractFilePaths extends Operation { + + /** + * ExtractFilePaths constructor + */ + constructor() { + super(); + + this.name = "Extract file paths"; + this.module = "Regex"; + this.description = "Extracts anything that looks like a Windows or UNIX file path.

Note that if UNIX is selected, there will likely be a lot of false positives."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Windows", + "type": "boolean", + "value": true + }, + { + "name": "UNIX", + "type": "boolean", + "value": true + }, + { + "name": "Display total", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const includeWinPath = args[0], + includeUnixPath = args[1], + displayTotal = args[2], + winDrive = "[A-Z]:\\\\", + winName = "[A-Z\\d][A-Z\\d\\- '_\\(\\)~]{0,61}", + winExt = "[A-Z\\d]{1,6}", + winPath = winDrive + "(?:" + winName + "\\\\?)*" + winName + + "(?:\\." + winExt + ")?", + unixPath = "(?:/[A-Z\\d.][A-Z\\d\\-.]{0,61})+"; + let filePaths = ""; + + if (includeWinPath && includeUnixPath) { + filePaths = winPath + "|" + unixPath; + } else if (includeWinPath) { + filePaths = winPath; + } else if (includeUnixPath) { + filePaths = unixPath; + } + + if (filePaths) { + const regex = new RegExp(filePaths, "ig"); + return search(input, regex, null, displayTotal); + } else { + return ""; + } + } + +} + +export default ExtractFilePaths; diff --git a/src/core/operations/ExtractIPAddresses.mjs b/src/core/operations/ExtractIPAddresses.mjs new file mode 100644 index 00000000..b69d97d0 --- /dev/null +++ b/src/core/operations/ExtractIPAddresses.mjs @@ -0,0 +1,94 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { search } from "../lib/Extract"; + +/** + * Extract IP addresses operation + */ +class ExtractIPAddresses extends Operation { + + /** + * ExtractIPAddresses constructor + */ + constructor() { + super(); + + this.name = "Extract IP addresses"; + this.module = "Regex"; + this.description = "Extracts all IPv4 and IPv6 addresses.

Warning: Given a string 710.65.0.456, this will match 10.65.0.45 so always check the original input!"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "IPv4", + "type": "boolean", + "value": true + }, + { + "name": "IPv6", + "type": "boolean", + "value": false + }, + { + "name": "Remove local IPv4 addresses", + "type": "boolean", + "value": false + }, + { + "name": "Display total", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const includeIpv4 = args[0], + includeIpv6 = args[1], + removeLocal = args[2], + displayTotal = args[3], + ipv4 = "(?:(?:\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d|\\d)(?:\\/\\d{1,2})?", + ipv6 = "((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})((([\\dA-F]{1,4}((?!\\3)::|:\\b|(?![\\dA-F])))|(?!\\2\\3)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})"; + let ips = ""; + + if (includeIpv4 && includeIpv6) { + ips = ipv4 + "|" + ipv6; + } else if (includeIpv4) { + ips = ipv4; + } else if (includeIpv6) { + ips = ipv6; + } + + if (ips) { + const regex = new RegExp(ips, "ig"); + + if (removeLocal) { + const ten = "10\\..+", + oneninetwo = "192\\.168\\..+", + oneseventwo = "172\\.(?:1[6-9]|2\\d|3[01])\\..+", + onetwoseven = "127\\..+", + removeRegex = new RegExp("^(?:" + ten + "|" + oneninetwo + + "|" + oneseventwo + "|" + onetwoseven + ")"); + + return search(input, regex, removeRegex, displayTotal); + } else { + return search(input, regex, null, displayTotal); + } + } else { + return ""; + } + } + +} + +export default ExtractIPAddresses; diff --git a/src/core/operations/ExtractMACAddresses.mjs b/src/core/operations/ExtractMACAddresses.mjs new file mode 100644 index 00000000..9c3c2a5b --- /dev/null +++ b/src/core/operations/ExtractMACAddresses.mjs @@ -0,0 +1,49 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { search } from "../lib/Extract"; + +/** + * Extract MAC addresses operation + */ +class ExtractMACAddresses extends Operation { + + /** + * ExtractMACAddresses constructor + */ + constructor() { + super(); + + this.name = "Extract MAC addresses"; + this.module = "Regex"; + this.description = "Extracts all Media Access Control (MAC) addresses from the input."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Display total", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const displayTotal = args[0], + regex = /[A-F\d]{2}(?:[:-][A-F\d]{2}){5}/ig; + + return search(input, regex, null, displayTotal); + } + +} + +export default ExtractMACAddresses; diff --git a/src/core/operations/ExtractURLs.mjs b/src/core/operations/ExtractURLs.mjs new file mode 100644 index 00000000..ab306d3f --- /dev/null +++ b/src/core/operations/ExtractURLs.mjs @@ -0,0 +1,55 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import { search } from "../lib/Extract"; + +/** + * Extract URLs operation + */ +class ExtractURLs extends Operation { + + /** + * ExtractURLs constructor + */ + constructor() { + super(); + + this.name = "Extract URLs"; + this.module = "Regex"; + this.description = "Extracts Uniform Resource Locators (URLs) from the input. The protocol (http, ftp etc.) is required otherwise there will be far too many false positives."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Display total", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const displayTotal = args[0], + protocol = "[A-Z]+://", + hostname = "[-\\w]+(?:\\.\\w[-\\w]*)+", + port = ":\\d+"; + let path = "/[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]*"; + + path += "(?:[.!,?]+[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]+)*"; + const regex = new RegExp(protocol + hostname + "(?:" + port + + ")?(?:" + path + ")?", "ig"); + return search(input, regex, null, displayTotal); + } + +} + +export default ExtractURLs; diff --git a/src/core/operations/Filter.mjs b/src/core/operations/Filter.mjs index 1782b38c..aaf53f0f 100644 --- a/src/core/operations/Filter.mjs +++ b/src/core/operations/Filter.mjs @@ -7,6 +7,7 @@ import Operation from "../Operation"; import Utils from "../Utils"; import {INPUT_DELIM_OPTIONS} from "../lib/Delim"; +import OperationError from "../errors/OperationError"; /** * Filter operation @@ -56,7 +57,7 @@ class Filter extends Operation { try { regex = new RegExp(args[1]); } catch (err) { - return "Invalid regex. Details: " + err.message; + throw new OperationError(`Invalid regex. Details: ${err.message}`); } const regexFilter = function(value) { diff --git a/src/core/operations/FromCharcode.mjs b/src/core/operations/FromCharcode.mjs index ed2197bc..2dee72f2 100644 --- a/src/core/operations/FromCharcode.mjs +++ b/src/core/operations/FromCharcode.mjs @@ -7,6 +7,7 @@ import Operation from "../Operation"; import Utils from "../Utils"; import {DELIM_OPTIONS} from "../lib/Delim"; +import OperationError from "../errors/OperationError"; /** * From Charcode operation @@ -42,6 +43,8 @@ class FromCharcode extends Operation { * @param {string} input * @param {Object[]} args * @returns {byteArray} + * + * @throws {OperationError} if base out of range */ run(input, args) { const delim = Utils.charRep(args[0] || "Space"), @@ -50,7 +53,7 @@ class FromCharcode extends Operation { i = 0; if (base < 2 || base > 36) { - throw "Error: Base argument must be between 2 and 36"; + throw new OperationError("Error: Base argument must be between 2 and 36"); } if (input.length === 0) { diff --git a/src/core/operations/FromUNIXTimestamp.mjs b/src/core/operations/FromUNIXTimestamp.mjs index 90c7f120..258b6808 100644 --- a/src/core/operations/FromUNIXTimestamp.mjs +++ b/src/core/operations/FromUNIXTimestamp.mjs @@ -7,6 +7,7 @@ import Operation from "../Operation"; import moment from "moment-timezone"; import {UNITS} from "../lib/DateTime"; +import OperationError from "../errors/OperationError"; /** * From UNIX Timestamp operation @@ -37,6 +38,8 @@ class FromUNIXTimestamp extends Operation { * @param {number} input * @param {Object[]} args * @returns {string} + * + * @throws {OperationError} if invalid unit */ run(input, args) { const units = args[0]; @@ -57,7 +60,7 @@ class FromUNIXTimestamp extends Operation { d = moment(input / 1000000); return d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss.SSS") + " UTC"; } else { - throw "Unrecognised unit"; + throw new OperationError("Unrecognised unit"); } } diff --git a/src/core/operations/GeneratePGPKeyPair.mjs b/src/core/operations/GeneratePGPKeyPair.mjs new file mode 100644 index 00000000..77a60fae --- /dev/null +++ b/src/core/operations/GeneratePGPKeyPair.mjs @@ -0,0 +1,115 @@ +/** + * @author tlwr [toby@toby.codes] + * @author Matt C [matt@artemisbot.uk] + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import kbpgp from "kbpgp"; +import { getSubkeySize, ASP } from "../lib/PGP"; +import promisifyDefault from "es6-promisify"; +const promisify = promisifyDefault.promisify; +/** + * Generate PGP Key Pair operation + */ +class GeneratePGPKeyPair extends Operation { + + /** + * GeneratePGPKeyPair constructor + */ + constructor() { + super(); + + this.name = "Generate PGP Key Pair"; + this.module = "PGP"; + this.description = "Generates a new public/private PGP key pair. Supports RSA and Eliptic Curve (EC) keys."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key type", + "type": "option", + "value": ["RSA-1024", "RSA-2048", "RSA-4096", "ECC-256", "ECC-384"] + }, + { + "name": "Password (optional)", + "type": "string", + "value": "" + }, + { + "name": "Name (optional)", + "type": "string", + "value": "" + }, + { + "name": "Email (optional)", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [keyType, keySize] = args[0].split("-"), + password = args[1], + name = args[2], + email = args[3]; + let 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; + + const keyGenerationOptions = { + userid: userIdentifier, + ecc: keyType === "ecc", + primary: { + "nbits": keySize, + "flags": flags, + "expire_in": 0 + }, + subkeys: [{ + "nbits": getSubkeySize(keySize), + "flags": kbpgp.const.openpgp.sign_data, + "expire_in": 86400 * 365 * 8 + }, { + "nbits": getSubkeySize(keySize), + "flags": kbpgp.const.openpgp.encrypt_comm | kbpgp.const.openpgp.encrypt_storage, + "expire_in": 86400 * 365 * 2 + }], + asp: ASP + }; + + return new Promise(async (resolve, reject) => { + try { + const unsignedKey = await promisify(kbpgp.KeyManager.generate)(keyGenerationOptions); + await promisify(unsignedKey.sign.bind(unsignedKey))({}); + + const signedKey = unsignedKey, + 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}`); + } + }); + } + +} + +export default GeneratePGPKeyPair; diff --git a/src/core/operations/HammingDistance.mjs b/src/core/operations/HammingDistance.mjs index 9e2a4550..173ca3e7 100644 --- a/src/core/operations/HammingDistance.mjs +++ b/src/core/operations/HammingDistance.mjs @@ -7,6 +7,7 @@ import Operation from "../Operation"; import Utils from "../Utils"; import {fromHex} from "../lib/Hex"; +import OperationError from "../errors/OperationError"; /** * Hamming Distance operation @@ -55,11 +56,11 @@ class HammingDistance extends Operation { samples = input.split(delim); if (samples.length !== 2) { - return "Error: You can only calculae the edit distance between 2 strings. Please ensure exactly two inputs are provided, separated by the specified delimiter."; + throw new OperationError("Error: You can only calculae the edit distance between 2 strings. Please ensure exactly two inputs are provided, separated by the specified delimiter."); } if (samples[0].length !== samples[1].length) { - return "Error: Both inputs must be of the same length."; + throw new OperationError("Error: Both inputs must be of the same length."); } if (inputType === "Hex") { diff --git a/src/core/operations/OffsetChecker.mjs b/src/core/operations/OffsetChecker.mjs index c49fdd1a..07b15d2f 100644 --- a/src/core/operations/OffsetChecker.mjs +++ b/src/core/operations/OffsetChecker.mjs @@ -6,6 +6,7 @@ import Operation from "../Operation"; import Utils from "../Utils"; +import OperationError from "../errors/OperationError"; /** * Offset checker operation @@ -48,7 +49,7 @@ class OffsetChecker extends Operation { chr; if (!samples || samples.length < 2) { - return "Not enough samples, perhaps you need to modify the sample delimiter or add more data?"; + throw new OperationError("Not enough samples, perhaps you need to modify the sample delimiter or add more data?"); } // Initialise output strings diff --git a/src/core/operations/PGPDecrypt.mjs b/src/core/operations/PGPDecrypt.mjs new file mode 100644 index 00000000..4402a457 --- /dev/null +++ b/src/core/operations/PGPDecrypt.mjs @@ -0,0 +1,79 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import kbpgp from "kbpgp"; +import { ASP, importPrivateKey } from "../lib/PGP"; +import OperationError from "../errors/OperationError"; +import promisifyDefault from "es6-promisify"; +const promisify = promisifyDefault.promisify; + + +/** + * PGP Decrypt operation + */ +class PGPDecrypt extends Operation { + + /** + * PGPDecrypt constructor + */ + constructor() { + super(); + + this.name = "PGP Decrypt"; + this.module = "PGP"; + this.description = "Input: the ASCII-armoured PGP message you want to decrypt.\n

\nArguments: the ASCII-armoured PGP private key of the recipient, \n(and the private key password if necessary).\n

\nPretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.\n

\nThis function uses the Keybase implementation of PGP."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Private key of recipient", + "type": "text", + "value": "" + }, + { + "name": "Private key passphrase", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if invalid private key + */ + async run(input, args) { + const encryptedMessage = input, + privateKey = args[0], + passphrase = args[1], + keyring = new kbpgp.keyring.KeyRing(); + let plaintextMessage; + + if (!privateKey) throw new OperationError("Enter the private key of the recipient."); + + const key = await importPrivateKey(privateKey, passphrase); + keyring.add_key_manager(key); + + try { + plaintextMessage = await promisify(kbpgp.unbox)({ + armored: encryptedMessage, + keyfetch: keyring, + asp: ASP + }); + } catch (err) { + throw new OperationError(`Couldn't decrypt message with provided private key: ${err}`); + } + + return plaintextMessage.toString(); + } + +} + +export default PGPDecrypt; diff --git a/src/core/operations/PGPDecryptAndVerify.mjs b/src/core/operations/PGPDecryptAndVerify.mjs new file mode 100644 index 00000000..b7874f83 --- /dev/null +++ b/src/core/operations/PGPDecryptAndVerify.mjs @@ -0,0 +1,113 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import kbpgp from "kbpgp"; +import { ASP, importPrivateKey, importPublicKey } from "../lib/PGP"; +import OperationError from "../errors/OperationError"; +import promisifyDefault from "es6-promisify"; +const promisify = promisifyDefault.promisify; + +/** + * PGP Decrypt and Verify operation + */ +class PGPDecryptAndVerify extends Operation { + + /** + * PGPDecryptAndVerify constructor + */ + constructor() { + super(); + + this.name = "PGP Decrypt and Verify"; + this.module = "PGP"; + this.description = "Input: the ASCII-armoured encrypted PGP message you want to verify.\n

\nArguments: the ASCII-armoured PGP public key of the signer, \nthe ASCII-armoured private key of the recipient (and the private key password if necessary).\n

\nThis operation uses PGP to decrypt and verify an encrypted digital signature.\n

\nPretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.\n

\nThis function uses the Keybase implementation of PGP."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Public key of signer", + "type": "text", + "value": "" + }, + { + "name": "Private key of recipient", + "type": "text", + "value": "" + }, + { + "name": "Private key password", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const signedMessage = input, + publicKey = args[0], + privateKey = args[1], + passphrase = args[2], + keyring = new kbpgp.keyring.KeyRing(); + let unboxedLiterals; + + if (!publicKey) throw new OperationError("Enter the public key of the signer."); + if (!privateKey) throw new OperationError("Enter the private key of the recipient."); + const privKey = await importPrivateKey(privateKey, passphrase); + const pubKey = await importPublicKey(publicKey); + keyring.add_key_manager(privKey); + keyring.add_key_manager(pubKey); + + try { + unboxedLiterals = await promisify(kbpgp.unbox)({ + armored: signedMessage, + keyfetch: keyring, + asp: 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 { + throw new OperationError("Could not identify a key manager."); + } + } else { + throw new OperationError("The data does not appear to be signed."); + } + } catch (err) { + throw new OperationError(`Couldn't verify message: ${err}`); + } + } + +} + +export default PGPDecryptAndVerify; diff --git a/src/core/operations/PGPEncrypt.mjs b/src/core/operations/PGPEncrypt.mjs new file mode 100644 index 00000000..ef0a4d28 --- /dev/null +++ b/src/core/operations/PGPEncrypt.mjs @@ -0,0 +1,77 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import kbpgp from "kbpgp"; +import { ASP } from "../lib/PGP"; +import OperationError from "../errors/OperationError"; +import promisifyDefault from "es6-promisify"; +const promisify = promisifyDefault.promisify; + +/** + * PGP Encrypt operation + */ +class PGPEncrypt extends Operation { + + /** + * PGPEncrypt constructor + */ + constructor() { + super(); + + this.name = "PGP Encrypt"; + this.module = "PGP"; + this.description = "Input: the message you want to encrypt.\n

\nArguments: the ASCII-armoured PGP public key of the recipient.\n

\nPretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.\n

\nThis function uses the Keybase implementation of PGP."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Public key of recipient", + "type": "text", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if failed private key import or failed encryption + */ + async run(input, args) { + const plaintextMessage = input, + plainPubKey = args[0]; + let key, + encryptedMessage; + + if (!plainPubKey) throw new OperationError("Enter the public key of the recipient."); + + try { + key = await promisify(kbpgp.KeyManager.import_from_armored_pgp)({ + armored: plainPubKey, + }); + } catch (err) { + throw new OperationError(`Could not import public key: ${err}`); + } + + try { + encryptedMessage = await promisify(kbpgp.box)({ + "msg": plaintextMessage, + "encrypt_for": key, + "asp": ASP + }); + } catch (err) { + throw new OperationError(`Couldn't encrypt message with provided public key: ${err}`); + } + + return encryptedMessage.toString(); + } + +} + +export default PGPEncrypt; diff --git a/src/core/operations/PGPEncryptAndSign.mjs b/src/core/operations/PGPEncryptAndSign.mjs new file mode 100644 index 00000000..5b03c937 --- /dev/null +++ b/src/core/operations/PGPEncryptAndSign.mjs @@ -0,0 +1,84 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import kbpgp from "kbpgp"; +import { ASP, importPrivateKey, importPublicKey } from "../lib/PGP"; +import OperationError from "../errors/OperationError"; +import promisifyDefault from "es6-promisify"; +const promisify = promisifyDefault.promisify; + +/** + * PGP Encrypt and Sign operation + */ +class PGPEncryptAndSign extends Operation { + + /** + * PGPEncryptAndSign constructor + */ + constructor() { + super(); + + this.name = "PGP Encrypt and Sign"; + this.module = "PGP"; + this.description = "Input: the cleartext you want to sign.\n

\nArguments: the ASCII-armoured private key of the signer (plus the private key password if necessary)\nand the ASCII-armoured PGP public key of the recipient.\n

\nThis operation uses PGP to produce an encrypted digital signature.\n

\nPretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.\n

\nThis function uses the Keybase implementation of PGP."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Private key of signer", + "type": "text", + "value": "" + }, + { + "name": "Private key passphrase", + "type": "string", + "value": "" + }, + { + "name": "Public key of recipient", + "type": "text", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * + * @throws {OperationError} if failure to sign message + */ + async run(input, args) { + const message = input, + privateKey = args[0], + passphrase = args[1], + publicKey = args[2]; + let signedMessage; + + if (!privateKey) throw new OperationError("Enter the private key of the signer."); + if (!publicKey) throw new OperationError("Enter the public key of the recipient."); + const privKey = await importPrivateKey(privateKey, passphrase); + const pubKey = await importPublicKey(publicKey); + + try { + signedMessage = await promisify(kbpgp.box)({ + "msg": message, + "encrypt_for": pubKey, + "sign_with": privKey, + "asp": ASP + }); + } catch (err) { + throw new OperationError(`Couldn't sign message: ${err}`); + } + + return signedMessage; + } + +} + +export default PGPEncryptAndSign; diff --git a/src/core/operations/ParseDateTime.mjs b/src/core/operations/ParseDateTime.mjs index c48f848b..bb88c95d 100644 --- a/src/core/operations/ParseDateTime.mjs +++ b/src/core/operations/ParseDateTime.mjs @@ -7,6 +7,7 @@ import Operation from "../Operation"; import moment from "moment-timezone"; import {DATETIME_FORMATS, FORMAT_EXAMPLES} from "../lib/DateTime"; +import OperationError from "../errors/OperationError"; /** * Parse DateTime operation @@ -59,7 +60,7 @@ class ParseDateTime extends Operation { date = moment.tz(input, inputFormat, inputTimezone); if (!date || date.format() === "Invalid date") throw Error; } catch (err) { - return "Invalid format.\n\n" + FORMAT_EXAMPLES; + throw new OperationError(`Invalid format.\n\n${FORMAT_EXAMPLES}`); } output += "Date: " + date.format("dddd Do MMMM YYYY") + diff --git a/src/core/operations/ParseUNIXFilePermissions.mjs b/src/core/operations/ParseUNIXFilePermissions.mjs index 829dda76..a86ed9ce 100644 --- a/src/core/operations/ParseUNIXFilePermissions.mjs +++ b/src/core/operations/ParseUNIXFilePermissions.mjs @@ -5,6 +5,7 @@ */ import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; /** * Parse UNIX file permissions operation @@ -169,7 +170,7 @@ class ParseUNIXFilePermissions extends Operation { } } } else { - return "Invalid input format.\nPlease enter the permissions in either octal (e.g. 755) or textual (e.g. drwxr-xr-x) format."; + throw new OperationError("Invalid input format.\nPlease enter the permissions in either octal (e.g. 755) or textual (e.g. drwxr-xr-x) format."); } output += "Textual representation: " + permsToStr(perms); diff --git a/src/core/operations/ParseURI.mjs b/src/core/operations/ParseURI.mjs new file mode 100644 index 00000000..a272ef53 --- /dev/null +++ b/src/core/operations/ParseURI.mjs @@ -0,0 +1,69 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import url from "url"; + +/** + * Parse URI operation + */ +class ParseURI extends Operation { + + /** + * ParseURI constructor + */ + constructor() { + super(); + + this.name = "Parse URI"; + this.module = "URL"; + this.description = "Pretty prints complicated Uniform Resource Identifier (URI) strings for ease of reading. Particularly useful for Uniform Resource Locators (URLs) with a lot of arguments."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const uri = url.parse(input, true); + + let output = ""; + + if (uri.protocol) output += "Protocol:\t" + uri.protocol + "\n"; + if (uri.auth) output += "Auth:\t\t" + uri.auth + "\n"; + if (uri.hostname) output += "Hostname:\t" + uri.hostname + "\n"; + if (uri.port) output += "Port:\t\t" + uri.port + "\n"; + if (uri.pathname) output += "Path name:\t" + uri.pathname + "\n"; + if (uri.query) { + const keys = Object.keys(uri.query); + let padding = 0; + + keys.forEach(k => { + padding = (k.length > padding) ? k.length : padding; + }); + + output += "Arguments:\n"; + for (const key in uri.query) { + output += "\t" + key.padEnd(padding, " "); + if (uri.query[key].length) { + output += " = " + uri.query[key] + "\n"; + } else { + output += "\n"; + } + } + } + if (uri.hash) output += "Hash:\t\t" + uri.hash + "\n"; + + return output; + } + +} + +export default ParseURI; diff --git a/src/core/operations/RawInflate.mjs b/src/core/operations/RawInflate.mjs index 93a7b91f..f1a5341b 100644 --- a/src/core/operations/RawInflate.mjs +++ b/src/core/operations/RawInflate.mjs @@ -7,6 +7,7 @@ import Operation from "../Operation"; import {INFLATE_BUFFER_TYPE} from "../lib/Zlib"; import rawinflate from "zlibjs/bin/rawinflate.min"; +import OperationError from "../errors/OperationError"; const Zlib = rawinflate.Zlib; @@ -90,7 +91,7 @@ class RawInflate extends Operation { } if (!valid) { - throw "Error: Unable to inflate data"; + throw new OperationError("Error: Unable to inflate data"); } } // This seems to be the easiest way... diff --git a/src/core/operations/ShowBase64Offsets.mjs b/src/core/operations/ShowBase64Offsets.mjs index 2f44296f..43bcbbba 100644 --- a/src/core/operations/ShowBase64Offsets.mjs +++ b/src/core/operations/ShowBase64Offsets.mjs @@ -7,6 +7,7 @@ import Operation from "../Operation"; import Utils from "../Utils"; import {fromBase64, toBase64} from "../lib/Base64"; +import OperationError from "../errors/OperationError"; /** * Show Base64 offsets operation @@ -58,7 +59,7 @@ class ShowBase64Offsets extends Operation { script = ""; if (input.length < 1) { - return "Please enter a string."; + throw new OperationError("Please enter a string."); } // Highlight offset 0 diff --git a/src/core/operations/Strings.mjs b/src/core/operations/Strings.mjs new file mode 100644 index 00000000..a833f6dc --- /dev/null +++ b/src/core/operations/Strings.mjs @@ -0,0 +1,118 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import XRegExp from "xregexp"; +import { search } from "../lib/Extract"; +/** + * Strings operation + */ +class Strings extends Operation { + + /** + * Strings constructor + */ + constructor() { + super(); + + this.name = "Strings"; + this.module = "Regex"; + this.description = "Extracts all strings from the input."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Encoding", + "type": "option", + "value": ["Single byte", "16-bit littleendian", "16-bit bigendian", "All"] + }, + { + "name": "Minimum length", + "type": "number", + "value": 4 + }, + { + "name": "Match", + "type": "option", + "value": [ + "[ASCII]", "Alphanumeric + punctuation (A)", "All printable chars (A)", "Null-terminated strings (A)", + "[Unicode]", "Alphanumeric + punctuation (U)", "All printable chars (U)", "Null-terminated strings (U)" + ] + }, + { + "name": "Display total", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const encoding = args[0], + minLen = args[1], + matchType = args[2], + displayTotal = args[3], + alphanumeric = "A-Z\\d", + punctuation = "/\\-:.,_$%'\"()<>= !\\[\\]{}@", + printable = "\x20-\x7e", + uniAlphanumeric = "\\pL\\pN", + uniPunctuation = "\\pP\\pZ", + uniPrintable = "\\pL\\pM\\pZ\\pS\\pN\\pP"; + + let strings = ""; + + switch (matchType) { + case "Alphanumeric + punctuation (A)": + strings = `[${alphanumeric + punctuation}]`; + break; + case "All printable chars (A)": + case "Null-terminated strings (A)": + strings = `[${printable}]`; + break; + case "Alphanumeric + punctuation (U)": + strings = `[${uniAlphanumeric + uniPunctuation}]`; + break; + case "All printable chars (U)": + case "Null-terminated strings (U)": + strings = `[${uniPrintable}]`; + break; + } + + // UTF-16 support is hacked in by allowing null bytes on either side of the matched chars + switch (encoding) { + case "All": + strings = `(\x00?${strings}\x00?)`; + break; + case "16-bit littleendian": + strings = `(${strings}\x00)`; + break; + case "16-bit bigendian": + strings = `(\x00${strings})`; + break; + case "Single byte": + default: + break; + } + + strings = `${strings}{${minLen},}`; + + if (matchType.includes("Null-terminated")) { + strings += "\x00"; + } + + const regex = new XRegExp(strings, "ig"); + + return search(input, regex, null, displayTotal); + } + +} + +export default Strings; diff --git a/src/core/operations/TakeBytes.mjs b/src/core/operations/TakeBytes.mjs index 1fec3997..5806ee87 100644 --- a/src/core/operations/TakeBytes.mjs +++ b/src/core/operations/TakeBytes.mjs @@ -5,6 +5,7 @@ */ import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; /** * Take bytes operation @@ -45,6 +46,8 @@ class TakeBytes extends Operation { * @param {ArrayBuffer} input * @param {Object[]} args * @returns {ArrayBuffer} + * + * @throws {OperationError} if invalid value */ run(input, args) { const start = args[0], @@ -52,7 +55,7 @@ class TakeBytes extends Operation { applyToEachLine = args[2]; if (start < 0 || length < 0) - throw "Error: Invalid value"; + throw new OperationError("Error: Invalid value"); if (!applyToEachLine) return input.slice(start, start+length); diff --git a/src/core/operations/ToCharcode.mjs b/src/core/operations/ToCharcode.mjs index c6943e90..db043d79 100644 --- a/src/core/operations/ToCharcode.mjs +++ b/src/core/operations/ToCharcode.mjs @@ -7,6 +7,7 @@ import Operation from "../Operation"; import Utils from "../Utils"; import {DELIM_OPTIONS} from "../lib/Delim"; +import OperationError from "../errors/OperationError"; /** * To Charcode operation @@ -42,6 +43,8 @@ class ToCharcode extends Operation { * @param {string} input * @param {Object[]} args * @returns {string} + * + * @throws {OperationError} if base argument out of range */ run(input, args) { const delim = Utils.charRep(args[0] || "Space"), @@ -51,7 +54,7 @@ class ToCharcode extends Operation { ordinal; if (base < 2 || base > 36) { - throw "Error: Base argument must be between 2 and 36"; + throw new OperationError("Error: Base argument must be between 2 and 36"); } const charcode = Utils.strToCharcode(input); diff --git a/src/core/operations/ToUNIXTimestamp.mjs b/src/core/operations/ToUNIXTimestamp.mjs index 1907b5a6..6983d617 100644 --- a/src/core/operations/ToUNIXTimestamp.mjs +++ b/src/core/operations/ToUNIXTimestamp.mjs @@ -7,6 +7,7 @@ import Operation from "../Operation"; import moment from "moment-timezone"; import {UNITS} from "../lib/DateTime"; +import OperationError from "../errors/OperationError"; /** * To UNIX Timestamp operation @@ -47,6 +48,8 @@ class ToUNIXTimestamp extends Operation { * @param {string} input * @param {Object[]} args * @returns {string} + * + * @throws {OperationError} if unit unrecognised */ run(input, args) { const [units, treatAsUTC, showDateTime] = args, @@ -63,7 +66,7 @@ class ToUNIXTimestamp extends Operation { } else if (units === "Nanoseconds (ns)") { result = d.valueOf() * 1000000; } else { - throw "Unrecognised unit"; + throw new OperationError("Unrecognised unit"); } return showDateTime ? `${result} (${d.tz("UTC").format("ddd D MMMM YYYY HH:mm:ss")} UTC)` : result.toString(); diff --git a/src/core/operations/TranslateDateTimeFormat.mjs b/src/core/operations/TranslateDateTimeFormat.mjs index b3895978..6ed72d9f 100644 --- a/src/core/operations/TranslateDateTimeFormat.mjs +++ b/src/core/operations/TranslateDateTimeFormat.mjs @@ -7,6 +7,7 @@ import Operation from "../Operation"; import moment from "moment-timezone"; import {DATETIME_FORMATS, FORMAT_EXAMPLES} from "../lib/DateTime"; +import OperationError from "../errors/OperationError"; /** * Translate DateTime Format operation @@ -67,7 +68,7 @@ class TranslateDateTimeFormat extends Operation { date = moment.tz(input, inputFormat, inputTimezone); if (!date || date.format() === "Invalid date") throw Error; } catch (err) { - return "Invalid format.\n\n" + FORMAT_EXAMPLES; + throw new OperationError(`Invalid format.\n\n${FORMAT_EXAMPLES}`); } return date.tz(outputTimezone).format(outputFormat); diff --git a/src/core/operations/URLDecode.mjs b/src/core/operations/URLDecode.mjs new file mode 100644 index 00000000..1d4555b0 --- /dev/null +++ b/src/core/operations/URLDecode.mjs @@ -0,0 +1,44 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * URL Decode operation + */ +class URLDecode extends Operation { + + /** + * URLDecode constructor + */ + constructor() { + super(); + + this.name = "URL Decode"; + this.module = "URL"; + this.description = "Converts URI/URL percent-encoded characters back to their raw values.

e.g. %3d becomes ="; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const data = input.replace(/\+/g, "%20"); + try { + return decodeURIComponent(data); + } catch (err) { + return unescape(data); + } + } + +} + +export default URLDecode; diff --git a/src/core/operations/URLEncode.mjs b/src/core/operations/URLEncode.mjs new file mode 100644 index 00000000..b1637594 --- /dev/null +++ b/src/core/operations/URLEncode.mjs @@ -0,0 +1,68 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * URL Encode operation + */ +class URLEncode extends Operation { + + /** + * URLEncode constructor + */ + constructor() { + super(); + + this.name = "URL Encode"; + this.module = "URL"; + this.description = "Encodes problematic characters into percent-encoding, a format supported by URIs/URLs.

e.g. = becomes %3d"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Encode all special chars", + "type": "boolean", + "value": false + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const encodeAll = args[0]; + return encodeAll ? this.encodeAllChars(input) : encodeURI(input); + } + + /** + * Encode characters in URL outside of encodeURI() function spec + * + * @param {string} str + * @returns {string} + */ + encodeAllChars (str) { + //TODO Do this programatically + return encodeURIComponent(str) + .replace(/!/g, "%21") + .replace(/#/g, "%23") + .replace(/'/g, "%27") + .replace(/\(/g, "%28") + .replace(/\)/g, "%29") + .replace(/\*/g, "%2A") + .replace(/-/g, "%2D") + .replace(/\./g, "%2E") + .replace(/_/g, "%5F") + .replace(/~/g, "%7E"); + } + +} + + +export default URLEncode; diff --git a/src/core/operations/legacy/Extract.js b/src/core/operations/legacy/Extract.js deleted file mode 100755 index 92c75a21..00000000 --- a/src/core/operations/legacy/Extract.js +++ /dev/null @@ -1,333 +0,0 @@ -import XRegExp from "xregexp"; - - -/** - * Identifier extraction operations. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Extract = { - - /** - * Runs search operations across the input data using regular expressions. - * - * @private - * @param {string} input - * @param {RegExp} searchRegex - * @param {RegExp} removeRegex - A regular expression defining results to remove from the - * final list - * @param {boolean} includeTotal - Whether or not to include the total number of results - * @returns {string} - */ - _search: function(input, searchRegex, removeRegex, includeTotal) { - let output = "", - total = 0, - match; - - while ((match = searchRegex.exec(input))) { - // Moves pointer when an empty string is matched (prevents infinite loop) - if (match.index === searchRegex.lastIndex) { - searchRegex.lastIndex++; - } - - if (removeRegex && removeRegex.test(match[0])) - continue; - total++; - output += match[0] + "\n"; - } - - if (includeTotal) - output = "Total found: " + total + "\n\n" + output; - - return output; - }, - - - /** - * @constant - * @default - */ - MIN_STRING_LEN: 4, - /** - * @constant - * @default - */ - STRING_MATCH_TYPE: [ - "[ASCII]", "Alphanumeric + punctuation (A)", "All printable chars (A)", "Null-terminated strings (A)", - "[Unicode]", "Alphanumeric + punctuation (U)", "All printable chars (U)", "Null-terminated strings (U)" - ], - /** - * @constant - * @default - */ - ENCODING_LIST: ["Single byte", "16-bit littleendian", "16-bit bigendian", "All"], - /** - * @constant - * @default - */ - DISPLAY_TOTAL: false, - - /** - * Strings operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runStrings: function(input, args) { - const encoding = args[0], - minLen = args[1], - matchType = args[2], - displayTotal = args[3], - alphanumeric = "A-Z\\d", - punctuation = "/\\-:.,_$%'\"()<>= !\\[\\]{}@", - printable = "\x20-\x7e", - uniAlphanumeric = "\\pL\\pN", - uniPunctuation = "\\pP\\pZ", - uniPrintable = "\\pL\\pM\\pZ\\pS\\pN\\pP"; - - let strings = ""; - - switch (matchType) { - case "Alphanumeric + punctuation (A)": - strings = `[${alphanumeric + punctuation}]`; - break; - case "All printable chars (A)": - case "Null-terminated strings (A)": - strings = `[${printable}]`; - break; - case "Alphanumeric + punctuation (U)": - strings = `[${uniAlphanumeric + uniPunctuation}]`; - break; - case "All printable chars (U)": - case "Null-terminated strings (U)": - strings = `[${uniPrintable}]`; - break; - } - - // UTF-16 support is hacked in by allowing null bytes on either side of the matched chars - switch (encoding) { - case "All": - strings = `(\x00?${strings}\x00?)`; - break; - case "16-bit littleendian": - strings = `(${strings}\x00)`; - break; - case "16-bit bigendian": - strings = `(\x00${strings})`; - break; - case "Single byte": - default: - break; - } - - strings = `${strings}{${minLen},}`; - - if (matchType.includes("Null-terminated")) { - strings += "\x00"; - } - - const regex = new XRegExp(strings, "ig"); - - return Extract._search(input, regex, null, displayTotal); - }, - - - /** - * @constant - * @default - */ - INCLUDE_IPV4: true, - /** - * @constant - * @default - */ - INCLUDE_IPV6: false, - /** - * @constant - * @default - */ - REMOVE_LOCAL: false, - - /** - * Extract IP addresses operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runIp: function(input, args) { - let includeIpv4 = args[0], - includeIpv6 = args[1], - removeLocal = args[2], - displayTotal = args[3], - ipv4 = "(?:(?:\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d|\\d)(?:\\/\\d{1,2})?", - ipv6 = "((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})((([\\dA-F]{1,4}((?!\\3)::|:\\b|(?![\\dA-F])))|(?!\\2\\3)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})", - ips = ""; - - if (includeIpv4 && includeIpv6) { - ips = ipv4 + "|" + ipv6; - } else if (includeIpv4) { - ips = ipv4; - } else if (includeIpv6) { - ips = ipv6; - } - - if (ips) { - const regex = new RegExp(ips, "ig"); - - if (removeLocal) { - let ten = "10\\..+", - oneninetwo = "192\\.168\\..+", - oneseventwo = "172\\.(?:1[6-9]|2\\d|3[01])\\..+", - onetwoseven = "127\\..+", - removeRegex = new RegExp("^(?:" + ten + "|" + oneninetwo + - "|" + oneseventwo + "|" + onetwoseven + ")"); - - return Extract._search(input, regex, removeRegex, displayTotal); - } else { - return Extract._search(input, regex, null, displayTotal); - } - } else { - return ""; - } - }, - - - /** - * Extract email addresses operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runEmail: function(input, args) { - let displayTotal = args[0], - regex = /\b\w[-.\w]*@[-\w]+(?:\.[-\w]+)*\.[A-Z]{2,4}\b/ig; - - return Extract._search(input, regex, null, displayTotal); - }, - - - /** - * Extract MAC addresses operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runMac: function(input, args) { - let displayTotal = args[0], - regex = /[A-F\d]{2}(?:[:-][A-F\d]{2}){5}/ig; - - return Extract._search(input, regex, null, displayTotal); - }, - - - /** - * Extract URLs operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runUrls: function(input, args) { - let displayTotal = args[0], - protocol = "[A-Z]+://", - hostname = "[-\\w]+(?:\\.\\w[-\\w]*)+", - port = ":\\d+", - path = "/[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]*"; - - path += "(?:[.!,?]+[^.!,?\"<>\\[\\]{}\\s\\x7F-\\xFF]+)*"; - const regex = new RegExp(protocol + hostname + "(?:" + port + - ")?(?:" + path + ")?", "ig"); - return Extract._search(input, regex, null, displayTotal); - }, - - - /** - * Extract domains operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runDomains: function(input, args) { - const displayTotal = args[0], - regex = /\b((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b/ig; - - return Extract._search(input, regex, null, displayTotal); - }, - - - /** - * @constant - * @default - */ - INCLUDE_WIN_PATH: true, - /** - * @constant - * @default - */ - INCLUDE_UNIX_PATH: true, - - /** - * Extract file paths operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runFilePaths: function(input, args) { - let includeWinPath = args[0], - includeUnixPath = args[1], - displayTotal = args[2], - winDrive = "[A-Z]:\\\\", - winName = "[A-Z\\d][A-Z\\d\\- '_\\(\\)~]{0,61}", - winExt = "[A-Z\\d]{1,6}", - winPath = winDrive + "(?:" + winName + "\\\\?)*" + winName + - "(?:\\." + winExt + ")?", - unixPath = "(?:/[A-Z\\d.][A-Z\\d\\-.]{0,61})+", - filePaths = ""; - - if (includeWinPath && includeUnixPath) { - filePaths = winPath + "|" + unixPath; - } else if (includeWinPath) { - filePaths = winPath; - } else if (includeUnixPath) { - filePaths = unixPath; - } - - if (filePaths) { - const regex = new RegExp(filePaths, "ig"); - return Extract._search(input, regex, null, displayTotal); - } else { - return ""; - } - }, - - - /** - * Extract dates operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runDates: function(input, args) { - let displayTotal = args[0], - date1 = "(?:19|20)\\d\\d[- /.](?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])", // yyyy-mm-dd - date2 = "(?:0[1-9]|[12][0-9]|3[01])[- /.](?:0[1-9]|1[012])[- /.](?:19|20)\\d\\d", // dd/mm/yyyy - date3 = "(?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])[- /.](?:19|20)\\d\\d", // mm/dd/yyyy - regex = new RegExp(date1 + "|" + date2 + "|" + date3, "ig"); - - return Extract._search(input, regex, null, displayTotal); - }, - -}; - -export default Extract; diff --git a/src/core/operations/legacy/PGP.js b/src/core/operations/legacy/PGP.js deleted file mode 100644 index b80ab4ae..00000000 --- a/src/core/operations/legacy/PGP.js +++ /dev/null @@ -1,364 +0,0 @@ -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; diff --git a/src/core/operations/legacy/URL.js b/src/core/operations/legacy/URL.js deleted file mode 100755 index 2f30c952..00000000 --- a/src/core/operations/legacy/URL.js +++ /dev/null @@ -1,118 +0,0 @@ -/* globals unescape */ -import url from "url"; - - -/** - * URL operations. - * Namespace is appended with an underscore to prevent overwriting the global URL object. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const URL_ = { - - /** - * @constant - * @default - */ - ENCODE_ALL: false, - - /** - * URL Encode operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runTo: function(input, args) { - const encodeAll = args[0]; - return encodeAll ? URL_._encodeAllChars(input) : encodeURI(input); - }, - - - /** - * URL Decode operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runFrom: function(input, args) { - const data = input.replace(/\+/g, "%20"); - try { - return decodeURIComponent(data); - } catch (err) { - return unescape(data); - } - }, - - - /** - * Parse URI operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {string} - */ - runParse: function(input, args) { - const uri = url.parse(input, true); - - let output = ""; - - if (uri.protocol) output += "Protocol:\t" + uri.protocol + "\n"; - if (uri.auth) output += "Auth:\t\t" + uri.auth + "\n"; - if (uri.hostname) output += "Hostname:\t" + uri.hostname + "\n"; - if (uri.port) output += "Port:\t\t" + uri.port + "\n"; - if (uri.pathname) output += "Path name:\t" + uri.pathname + "\n"; - if (uri.query) { - let keys = Object.keys(uri.query), - padding = 0; - - keys.forEach(k => { - padding = (k.length > padding) ? k.length : padding; - }); - - output += "Arguments:\n"; - for (let key in uri.query) { - output += "\t" + key.padEnd(padding, " "); - if (uri.query[key].length) { - output += " = " + uri.query[key] + "\n"; - } else { - output += "\n"; - } - } - } - if (uri.hash) output += "Hash:\t\t" + uri.hash + "\n"; - - return output; - }, - - - /** - * URL encodes additional special characters beyond the standard set. - * - * @private - * @param {string} str - * @returns {string} - */ - _encodeAllChars: function(str) { - //TODO Do this programatically - return encodeURIComponent(str) - .replace(/!/g, "%21") - .replace(/#/g, "%23") - .replace(/'/g, "%27") - .replace(/\(/g, "%28") - .replace(/\)/g, "%29") - .replace(/\*/g, "%2A") - .replace(/-/g, "%2D") - .replace(/\./g, "%2E") - .replace(/_/g, "%5F") - .replace(/~/g, "%7E"); - }, - -}; - -export default URL_; diff --git a/src/core/vendor/DisassembleX86-64.js b/src/core/vendor/DisassembleX86-64.mjs similarity index 99% rename from src/core/vendor/DisassembleX86-64.js rename to src/core/vendor/DisassembleX86-64.mjs index e320c6c2..f0d30511 100644 --- a/src/core/vendor/DisassembleX86-64.js +++ b/src/core/vendor/DisassembleX86-64.mjs @@ -3316,7 +3316,7 @@ If input "type" is set 5 it will adjust the mnemonic array to decode Centaur ins If input "type" is set 6 it will adjust the mnemonic array to decode instruction for the X86/486 CPU which conflict with the vector unit instructions with UMOV. -------------------------------------------------------------------------------------------------------------------------*/ -function CompatibilityMode( type ) +export function CompatibilityMode( type ) { //Reset the changeable sections of the Mnemonics array, and operand encoding array. @@ -3515,7 +3515,7 @@ The function "GetPosition()" Gives back the current base address in it's proper If the hex input is invalid returns false. -------------------------------------------------------------------------------------------------------------------------*/ -function LoadBinCode( HexStr ) +export function LoadBinCode( HexStr ) { //Clear BinCode, and Reset Code Position in Bin Code array. @@ -3605,7 +3605,7 @@ segment, and offset address. Note that the Code Segment is used in 16 bit code. if set 36, or higher. Effects instruction location in memory when decoding a program. -------------------------------------------------------------------------------------------------------------------------*/ -function SetBasePosition( Address ) +export function SetBasePosition( Address ) { //Split the Segment:offset. @@ -5652,7 +5652,7 @@ function Reset() do an linear disassemble. -------------------------------------------------------------------------------------------------------------------------*/ -function LDisassemble() +export function LDisassemble() { var Instruction = ""; //Stores the Decoded instruction. var Out = ""; //The Disassemble output @@ -5709,13 +5709,13 @@ function LDisassemble() * The following code has been added to expose public methods for use in CyberChef */ -export default { - LoadBinCode: LoadBinCode, - LDisassemble: LDisassemble, - SetBasePosition: SetBasePosition, - CompatibilityMode: CompatibilityMode, - - setBitMode: val => { BitMode = val; }, - setShowInstructionHex: val => { ShowInstructionHex = val; }, - setShowInstructionPos: val => { ShowInstructionPos = val; }, +export function setBitMode (val) { + BitMode = val; }; +export function setShowInstructionHex (val) { + ShowInstructionHex = val; +}; +export function setShowInstructionPos (val) { + ShowInstructionPos = val; +}; +