diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 6b12afbb79..f590b02c8e 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -122,7 +122,8 @@ "PGP Encrypt", "PGP Decrypt", "PGP Encrypt and Sign", - "PGP Decrypt and Verify" + "PGP Decrypt and Verify", + "Parse SSH Host Key" ] }, { diff --git a/src/core/operations/ParseSSHHostKey.mjs b/src/core/operations/ParseSSHHostKey.mjs new file mode 100644 index 0000000000..17b1a8d189 --- /dev/null +++ b/src/core/operations/ParseSSHHostKey.mjs @@ -0,0 +1,150 @@ +/** + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import Utils from "../Utils"; +import { fromBase64 } from "../lib/Base64"; +import { fromHex, toHexFast } from "../lib/Hex"; + +/** + * Parse SSH Host Key operation + */ +class ParseSSHHostKey extends Operation { + + /** + * ParseSSHHostKey constructor + */ + constructor() { + super(); + + this.name = "Parse SSH Host Key"; + this.module = "Default"; + this.description = "Parses a SSH host key and extracts fields from it.
The key type can be:The key format can be either Hex or Base64."; + this.infoURL = "https://wikipedia.org/wiki/Secure_Shell"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Input Format", + type: "option", + value: [ + "Auto", + "Base64", + "Hex" + ] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [inputFormat] = args, + inputKey = this.convertKeyToBinary(input.trim(), inputFormat), + fields = this.parseKey(inputKey), + keyType = Utils.byteArrayToChars(fromHex(fields[0]), ""); + + let output = `Key type: ${keyType}`; + + if (keyType === "ssh-rsa") { + output += `\nExponent: 0x${fields[1]}`; + output += `\nModulus: 0x${fields[2]}`; + } else if (keyType === "ssh-dss") { + output += `\np: 0x${fields[1]}`; + output += `\nq: 0x${fields[2]}`; + output += `\ng: 0x${fields[3]}`; + output += `\ny: 0x${fields[4]}`; + } else if (keyType.startsWith("ecdsa-sha2")) { + output += `\nCurve: ${Utils.byteArrayToChars(fromHex(fields[1]))}`; + output += `\nPoint: 0x${fields.slice(2)}`; + } else { + output += "\nUnsupported key type."; + output += `\nParameters: ${fields.slice(1)}`; + } + + return output; + } + + /** + * Converts the key to binary format from either hex or base64 + * + * @param {string} inputKey + * @param {string} inputFormat + * @returns {byteArray} + */ + convertKeyToBinary(inputKey, inputFormat) { + const keyPattern = new RegExp(/^(?:[ssh]|[ecdsa-sha2])\S+\s+(\S*)/), + keyMatch = inputKey.match(keyPattern); + + if (keyMatch) { + inputKey = keyMatch[1]; + } + + if (inputFormat === "Auto") { + inputFormat = this.detectKeyFormat(inputKey); + } + if (inputFormat === "Hex") { + return fromHex(inputKey); + } else if (inputFormat === "Base64") { + return fromBase64(inputKey, null, "byteArray"); + } else { + throw new OperationError("Invalid input format."); + } + } + + + /** + * Detects if the key is base64 or hex encoded + * + * @param {string} inputKey + * @returns {string} + */ + detectKeyFormat(inputKey) { + const hexPattern = new RegExp(/^(?:[\dA-Fa-f]{2}[ ,;:]?)+$/); + const b64Pattern = new RegExp(/^\s*(?:[A-Za-z\d+/]{4})+(?:[A-Za-z\d+/]{2}==|[A-Za-z\d+/]{3}=)?\s*$/); + + if (hexPattern.test(inputKey)) { + return "Hex"; + } else if (b64Pattern.test(inputKey)) { + return "Base64"; + } else { + throw new OperationError("Unable to detect input key format."); + } + } + + + /** + * Parses fields from the key + * + * @param {byteArray} key + */ + parseKey(key) { + const fields = []; + while (key.length > 0) { + const lengthField = key.slice(0, 4); + let decodedLength = 0; + for (let i = 0; i < lengthField.length; i++) { + decodedLength += lengthField[i]; + decodedLength = decodedLength << 8; + } + decodedLength = decodedLength >> 8; + // Break if length wasn't decoded correctly + if (decodedLength <= 0) break; + + fields.push(toHexFast(key.slice(4, 4 + decodedLength))); + key = key.slice(4 + decodedLength); + } + + return fields; + } + +} + +export default ParseSSHHostKey; diff --git a/tests/node/tests/nodeApi.mjs b/tests/node/tests/nodeApi.mjs index e775f3a0de..954a19ce77 100644 --- a/tests/node/tests/nodeApi.mjs +++ b/tests/node/tests/nodeApi.mjs @@ -136,7 +136,7 @@ TestRegister.addApiTests([ it("chef.help: returns multiple results", () => { const result = chef.help("base 64"); - assert.strictEqual(result.length, 10); + assert.strictEqual(result.length, 11); }), it("chef.help: looks in description for matches too", () => { diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 20750fb168..fc8d978ee8 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -86,6 +86,7 @@ import "./tests/Typex"; import "./tests/BLAKE2b"; import "./tests/BLAKE2s"; import "./tests/Protobuf"; +import "./tests/ParseSSHHostKey"; // Cannot test operations that use the File type yet //import "./tests/SplitColourChannels"; diff --git a/tests/operations/tests/ParseSSHHostKey.mjs b/tests/operations/tests/ParseSSHHostKey.mjs new file mode 100644 index 0000000000..00e13c6cc4 --- /dev/null +++ b/tests/operations/tests/ParseSSHHostKey.mjs @@ -0,0 +1,65 @@ +/** + * Parse SSH Host Key tests + * + * @author j433866 [j433866@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "SSH Host Key: RSA", + input: "AAAAB3NzaC1yc2EAAAADAQABAAABAQDiJZ/9W9Ix/Dk9b+K4E+RGCug1AtkGXaJ9vNIY0YHFHLpWsB8DAuh/cGEI9TLbL1gzR2wG+RJNQ2EAQVWe6ypkK63Jm4zw4re+vhEiszpnP889J0h5N9yzyTndesrl4d3cQtv861FcKDPxUJbRALdtl6gwOB7BCL8gsXJLLVLO4EesrbPXD454qpVt7CgJXEXByOFjcIm3XwkdOnXMPHHnMSD7EIN1SvQMD6PfIDrbDd6KQt5QXW/Rc/BsfX5cbUIV1QW5A/GbepXHHKmWRtLC2J/mH3hW2Zq/hITPEaJdG1CtIilQmJaZGXpfGIwFeb0Av9pSL926arZZ6vDi9ctF", + expectedOutput: `Key type: ssh-rsa +Exponent: 0x010001 +Modulus: 0x00e2259ffd5bd231fc393d6fe2b813e4460ae83502d9065da27dbcd218d181c51cba56b01f0302e87f706108f532db2f5833476c06f9124d43610041559eeb2a642badc99b8cf0e2b7bebe1122b33a673fcf3d27487937dcb3c939dd7acae5e1dddc42dbfceb515c2833f15096d100b76d97a830381ec108bf20b1724b2d52cee047acadb3d70f8e78aa956dec28095c45c1c8e1637089b75f091d3a75cc3c71e73120fb1083754af40c0fa3df203adb0dde8a42de505d6fd173f06c7d7e5c6d4215d505b903f19b7a95c71ca99646d2c2d89fe61f7856d99abf8484cf11a25d1b50ad222950989699197a5f188c0579bd00bfda522fddba6ab659eaf0e2f5cb45`, + recipeConfig: [ + { + op: "Parse SSH Host Key", + args: ["Base64"] + } + ] + }, + { + name: "SSH Host Key: DSA", + input: "AAAAB3NzaC1kc3MAAACBAMnoZCOzvaQqs//9mxK2USZvJBc7b1dFJiBcV80abN6maE+203pTRPIPCpPt0deQxv4YN3dSHoodEcArWxs1QRAIuRsQIvsUP7chovzGnxP84XWK5sbfrseD0vxZ7UR0NaAFPcSgeXcWC1SG9uvrAJQlyp4DBy+fKuqiYmwaz0bHAAAAFQCXNJ4yiE1V7LpCU2V1JKbqDvICMwAAAIB/5aR1iBOeyCVpj0dP3YZmoxd9R7FCC/0UuOf0lx4E6WHT6Z2QuPBhc2mpNDq2M0VF9oJfVWgcfG8r1rlXaCYODSacGcbnW5VKQ+LKkkALmg4h8jFCHReUC+Hmia/v8LyDwPO1wK6ETn7a3m80yM7gAU5ZNurVIVVP2lB65mjEsQAAAIA3ct9YRB6iUCvOD45sZM1C9oTC24Ttmaou0GcpWx3h0/iZ8mbil1cjaO9frRNZ/vSSVWEhEDNG8gwkjZWlvnJL3y1XUxbMll4WbmI/Q1kzKwopceaFwMbYTPKDg6L1RtCMUxSUyKsFk1c4SpEPlDS7DApZs5PgmWgMd/u6vwMXyg==", + expectedOutput: `Key type: ssh-dss +p: 0x00c9e86423b3bda42ab3fffd9b12b651266f24173b6f574526205c57cd1a6cdea6684fb6d37a5344f20f0a93edd1d790c6fe183777521e8a1d11c02b5b1b35411008b91b1022fb143fb721a2fcc69f13fce1758ae6c6dfaec783d2fc59ed447435a0053dc4a07977160b5486f6ebeb009425ca9e03072f9f2aeaa2626c1acf46c7 +q: 0x0097349e32884d55ecba4253657524a6ea0ef20233 +g: 0x7fe5a47588139ec825698f474fdd8666a3177d47b1420bfd14b8e7f4971e04e961d3e99d90b8f0617369a9343ab6334545f6825f55681c7c6f2bd6b95768260e0d269c19c6e75b954a43e2ca92400b9a0e21f231421d17940be1e689afeff0bc83c0f3b5c0ae844e7edade6f34c8cee0014e5936ead521554fda507ae668c4b1 +y: 0x3772df58441ea2502bce0f8e6c64cd42f684c2db84ed99aa2ed067295b1de1d3f899f266e297572368ef5fad1359fef492556121103346f20c248d95a5be724bdf2d575316cc965e166e623f4359332b0a2971e685c0c6d84cf28383a2f546d08c531494c8ab059357384a910f9434bb0c0a59b393e099680c77fbbabf0317ca`, + recipeConfig: [ + { + op: "Parse SSH Host Key", + args: ["Base64"] + } + ] + }, + { + name: "SSH Host Key: ECDSA", + input: "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGxZWSAGJyJQoVBwFCpr420eRUZDE/kw2YWm5vDro8050DZ1ZzqIuYaNl0BGzMcRTeasGtJuI8G84ZQQSgca3C4=", + expectedOutput: `Key type: ecdsa-sha2-nistp256 +Curve: nistp256 +Point: 0x046c59592006272250a15070142a6be36d1e45464313f930d985a6e6f0eba3cd39d03675673a88b9868d974046ccc7114de6ac1ad26e23c1bce194104a071adc2e`, + recipeConfig: [ + { + op: "Parse SSH Host Key", + args: ["Base64"] + } + ] + }, + { + name: "SSH Host Key: Extract key", + input: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDiJZ/9W9Ix/Dk9b+K4E+RGCug1AtkGXaJ9vNIY0YHFHLpWsB8DAuh/cGEI9TLbL1gzR2wG+RJNQ2EAQVWe6ypkK63Jm4zw4re+vhEiszpnP889J0h5N9yzyTndesrl4d3cQtv861FcKDPxUJbRALdtl6gwOB7BCL8gsXJLLVLO4EesrbPXD454qpVt7CgJXEXByOFjcIm3XwkdOnXMPHHnMSD7EIN1SvQMD6PfIDrbDd6KQt5QXW/Rc/BsfX5cbUIV1QW5A/GbepXHHKmWRtLC2J/mH3hW2Zq/hITPEaJdG1CtIilQmJaZGXpfGIwFeb0Av9pSL926arZZ6vDi9ctF test@test", + expectedOutput: `Key type: ssh-rsa +Exponent: 0x010001 +Modulus: 0x00e2259ffd5bd231fc393d6fe2b813e4460ae83502d9065da27dbcd218d181c51cba56b01f0302e87f706108f532db2f5833476c06f9124d43610041559eeb2a642badc99b8cf0e2b7bebe1122b33a673fcf3d27487937dcb3c939dd7acae5e1dddc42dbfceb515c2833f15096d100b76d97a830381ec108bf20b1724b2d52cee047acadb3d70f8e78aa956dec28095c45c1c8e1637089b75f091d3a75cc3c71e73120fb1083754af40c0fa3df203adb0dde8a42de505d6fd173f06c7d7e5c6d4215d505b903f19b7a95c71ca99646d2c2d89fe61f7856d99abf8484cf11a25d1b50ad222950989699197a5f188c0579bd00bfda522fddba6ab659eaf0e2f5cb45`, + recipeConfig: [ + { + op: "Parse SSH Host Key", + args: ["Base64"] + } + ] + } +]);