diff --git a/package-lock.json b/package-lock.json index 89f59346d..e0dab51e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@astronautlabs/amf": "^0.0.6", "@babel/polyfill": "^7.12.1", "@blu3r4y/lzma": "^2.3.3", + "@dnspect/dns-ts": "^0.2.3", "@wavesenterprise/crypto-gost-js": "^2.1.0-RC1", "@xmldom/xmldom": "^0.8.0", "argon2-browser": "^1.18.0", @@ -2038,6 +2039,20 @@ "node": ">=10.0.0" } }, + "node_modules/@dnspect/dns-ts": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@dnspect/dns-ts/-/dns-ts-0.2.3.tgz", + "integrity": "sha512-JH0OQad3RGkYiSYzI+bXVwXqmB/M+EWzGGTzHriB0WssdlcF0RfR5hBjPd0sF0uxQuPmfaKShw+jcDNgjkBmSw==", + "dependencies": { + "@dnspect/ip-address-ts": "0.2", + "base64-js": "1" + } + }, + "node_modules/@dnspect/ip-address-ts": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@dnspect/ip-address-ts/-/ip-address-ts-0.2.1.tgz", + "integrity": "sha512-iJsRv0nychs5GQM7K9woY5iZp4i8We3a7KG3t77QnV2DSvGiwERZQliR4oBqMb6RjHBPckX60p1ONCcXHVrXTw==" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", diff --git a/package.json b/package.json index cd5d4ca59..be3cc7ba3 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "@astronautlabs/amf": "^0.0.6", "@babel/polyfill": "^7.12.1", "@blu3r4y/lzma": "^2.3.3", + "@dnspect/dns-ts": "^0.2.3", "@wavesenterprise/crypto-gost-js": "^2.1.0-RC1", "@xmldom/xmldom": "^0.8.0", "argon2-browser": "^1.18.0", diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index bebdd6a5e..9f9e75fdb 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -238,6 +238,7 @@ "Parse UDP", "Parse SSH Host Key", "Parse URI", + "Parse DNS Message", "URL Encode", "URL Decode", "Protobuf Decode", diff --git a/src/core/operations/ParseDNSMessage.mjs b/src/core/operations/ParseDNSMessage.mjs new file mode 100644 index 000000000..92271a6a2 --- /dev/null +++ b/src/core/operations/ParseDNSMessage.mjs @@ -0,0 +1,55 @@ +/** + * @author Minghang Chen [chen@minghang.dev] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { Message } from "@dnspect/dns-ts"; + +/** + * Parse DNS Message operation + */ +class ParseDNSMessage extends Operation { + /** + * ParseDNSMessage constructor + */ + constructor() { + super(); + + this.name = "Parse DNS Message"; + this.module = "Default"; + this.description = "Parse the DNS wireformat binary of a DNS message and return a text representation"; + this.infoURL = "https://en.wikipedia.org/wiki/Domain_Name_System#DNS_message_format"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [{ + "name": "Output format", + "type": "option", + "value": ["dig-like", "dns-json"] + }]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const format = args[0]; + let msg; + try { + msg = Message.unpack(input); + } catch (e) { + throw new OperationError(`Malformed DNS message: ${e}`); + } + + switch (format) { + case "dig-like": return msg.toString(); + case "dns-json": return JSON.stringify(msg.toJsonObject(), null, 2); + default: throw new OperationError(`Unsupported output format: ${format}`); + } + } +} + +export default ParseDNSMessage; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 40ce7a2ee..f09cf2bcd 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -117,6 +117,7 @@ import "./tests/ParseSSHHostKey.mjs"; import "./tests/ParseTCP.mjs"; import "./tests/ParseTLV.mjs"; import "./tests/ParseUDP.mjs"; +import "./tests/ParseDNSMessage.mjs"; import "./tests/PEMtoHex.mjs"; import "./tests/PGP.mjs"; import "./tests/PHP.mjs"; diff --git a/tests/operations/tests/ParseDNSMessage.mjs b/tests/operations/tests/ParseDNSMessage.mjs new file mode 100644 index 000000000..84d118ed2 --- /dev/null +++ b/tests/operations/tests/ParseDNSMessage.mjs @@ -0,0 +1,84 @@ +/** + * Parse DNS Message tests. + * + * @author Minghang Chen [chen@minghang.dev] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Parse DNS Message: No Data", + input: "", + expectedOutput: "Malformed DNS message: ParseError: insufficient bytes remaining for read: needs 12, have 0", + recipeConfig: [ + { + op: "Parse DNS Message", + args: ["dig-like"], + }, + ], + }, + { + name: "Parse DNS Message: Malformed", + input: "\xab\xcd\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03\x77\x77\x77\x07\x65\x78\x61\x6d\x70\x6c\x65\x03\x63\x6f", + expectedOutput: "Malformed DNS message: RangeError: try to access beyond buffer length: read 3 start from 25", + recipeConfig: [ + { + op: "Parse DNS Message", + args: ["dig-like"], + } + ], + }, + { + name: "Parse DNS Message: dig-like", + input: "\xab\xcd\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03\x77\x77\x77\x07\x65\x78\x61\x6d\x70\x6c\x65\x03\x63\x6f\x6d\x00\x00\x01\x00\x01", + expectedOutput: `;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 43981 +;; flags: rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0 + +;; QUESTION SECTION: +;www.example.com. IN A`, + recipeConfig: [ + { + op: "Parse DNS Message", + args: ["dig-like"], + } + ], + }, + { + name: "Parse DNS Message: dns-json", + input: "\xab\xcd\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03\x77\x77\x77\x07\x65\x78\x61\x6d\x70\x6c\x65\x03\x63\x6f\x6d\x00\x00\x01\x00\x01", + expectedOutput: `{ + "Status": 0, + "TC": false, + "RD": true, + "RA": false, + "AD": false, + "CD": false, + "Question": [ + { + "name": "www.example.com.", + "type": 1 + } + ], + "Answer": [] +}`, + recipeConfig: [ + { + op: "Parse DNS Message", + args: ["dns-json"], + } + ], + }, + { + name: "Parse DNS Message: unsupported-output-format", + input: "\xab\xcd\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03\x77\x77\x77\x07\x65\x78\x61\x6d\x70\x6c\x65\x03\x63\x6f\x6d\x00\x00\x01\x00\x01", + expectedOutput: "Unsupported output format: invalid", + recipeConfig: [ + { + op: "Parse DNS Message", + args: ["invalid"], + } + ], + } +]);