From e9ca4dc9caf98f33fd986431cd400c88082a42b8 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Tue, 10 Aug 2021 16:48:35 +0100 Subject: [PATCH] Added HASSH operations --- src/core/config/Categories.json | 2 + .../operations/HASSHClientFingerprint.mjs | 166 ++++++++++++++++++ .../operations/HASSHServerFingerprint.mjs | 166 ++++++++++++++++++ src/core/operations/JA3Fingerprint.mjs | 2 +- src/core/operations/JA3SFingerprint.mjs | 2 +- tests/operations/index.mjs | 2 + tests/operations/tests/HASSH.mjs | 33 ++++ tests/operations/tests/JA3SFingerprint.mjs | 24 +-- 8 files changed, 384 insertions(+), 13 deletions(-) create mode 100644 src/core/operations/HASSHClientFingerprint.mjs create mode 100644 src/core/operations/HASSHServerFingerprint.mjs create mode 100644 tests/operations/tests/HASSH.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 5d729e32..d04ccb6b 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -195,6 +195,8 @@ "VarInt Decode", "JA3 Fingerprint", "JA3S Fingerprint", + "HASSH Client Fingerprint", + "HASSH Server Fingerprint", "Format MAC addresses", "Change IP format", "Group IP addresses", diff --git a/src/core/operations/HASSHClientFingerprint.mjs b/src/core/operations/HASSHClientFingerprint.mjs new file mode 100644 index 00000000..e507cea1 --- /dev/null +++ b/src/core/operations/HASSHClientFingerprint.mjs @@ -0,0 +1,166 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * HASSH created by Salesforce + * Ben Reardon (@benreardon) + * Adel Karimi (@0x4d31) + * and the JA3 crew: + * John B. Althouse + * Jeff Atkinson + * Josh Atkins + * + * Algorithm released under the BSD-3-clause licence + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import Stream from "../lib/Stream.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * HASSH Client Fingerprint operation + */ +class HASSHClientFingerprint extends Operation { + + /** + * HASSHClientFingerprint constructor + */ + constructor() { + super(); + + this.name = "HASSH Client Fingerprint"; + this.module = "Crypto"; + this.description = "Generates a HASSH fingerprint to help identify SSH clients based on hashing together values from the Client Key Exchange Init message.

Input: A hex stream of the SSH_MSG_KEXINIT packet application layer from Client to Server."; + this.infoURL = "https://engineering.salesforce.com/open-sourcing-hassh-abed3ae5044c"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Input format", + type: "option", + value: ["Hex", "Base64", "Raw"] + }, + { + name: "Output format", + type: "option", + value: ["Hash digest", "HASSH algorithms string", "Full details"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [inputFormat, outputFormat] = args; + + input = Utils.convertToByteArray(input, inputFormat); + const s = new Stream(new Uint8Array(input)); + + // Length + const length = s.readInt(4); + if (s.length !== length + 4) + throw new OperationError("Incorrect packet length."); + + // Padding length + const paddingLength = s.readInt(1); + + // Message code + const messageCode = s.readInt(1); + if (messageCode !== 20) + throw new OperationError("Not a Key Exchange Init."); + + // Cookie + s.moveForwardsBy(16); + + // KEX Algorithms + const kexAlgosLength = s.readInt(4); + const kexAlgos = s.readString(kexAlgosLength); + + // Server Host Key Algorithms + const serverHostKeyAlgosLength = s.readInt(4); + s.moveForwardsBy(serverHostKeyAlgosLength); + + // Encryption Algorithms Client to Server + const encAlgosC2SLength = s.readInt(4); + const encAlgosC2S = s.readString(encAlgosC2SLength); + + // Encryption Algorithms Server to Client + const encAlgosS2CLength = s.readInt(4); + s.moveForwardsBy(encAlgosS2CLength); + + // MAC Algorithms Client to Server + const macAlgosC2SLength = s.readInt(4); + const macAlgosC2S = s.readString(macAlgosC2SLength); + + // MAC Algorithms Server to Client + const macAlgosS2CLength = s.readInt(4); + s.moveForwardsBy(macAlgosS2CLength); + + // Compression Algorithms Client to Server + const compAlgosC2SLength = s.readInt(4); + const compAlgosC2S = s.readString(compAlgosC2SLength); + + // Compression Algorithms Server to Client + const compAlgosS2CLength = s.readInt(4); + s.moveForwardsBy(compAlgosS2CLength); + + // Languages Client to Server + const langsC2SLength = s.readInt(4); + s.moveForwardsBy(langsC2SLength); + + // Languages Server to Client + const langsS2CLength = s.readInt(4); + s.moveForwardsBy(langsS2CLength); + + // First KEX packet follows + s.moveForwardsBy(1); + + // Reserved + s.moveForwardsBy(4); + + // Padding string + s.moveForwardsBy(paddingLength); + + // Output + const hassh = [ + kexAlgos, + encAlgosC2S, + macAlgosC2S, + compAlgosC2S + ]; + const hasshStr = hassh.join(";"); + const hasshHash = runHash("md5", Utils.strToArrayBuffer(hasshStr)); + + switch (outputFormat) { + case "HASSH algorithms string": + return hasshStr; + case "Full details": + return `Hash digest: +${hasshHash} + +Full HASSH algorithms string: +${hasshStr} + +Key Exchange Algorithms: +${kexAlgos} +Encryption Algorithms Client to Server: +${encAlgosC2S} +MAC Algorithms Client to Server: +${macAlgosC2S} +Compression Algorithms Client to Server: +${compAlgosC2S}`; + case "Hash digest": + default: + return hasshHash; + } + } + +} + +export default HASSHClientFingerprint; diff --git a/src/core/operations/HASSHServerFingerprint.mjs b/src/core/operations/HASSHServerFingerprint.mjs new file mode 100644 index 00000000..f08a418b --- /dev/null +++ b/src/core/operations/HASSHServerFingerprint.mjs @@ -0,0 +1,166 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + * + * HASSH created by Salesforce + * Ben Reardon (@benreardon) + * Adel Karimi (@0x4d31) + * and the JA3 crew: + * John B. Althouse + * Jeff Atkinson + * Josh Atkins + * + * Algorithm released under the BSD-3-clause licence +*/ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import Stream from "../lib/Stream.mjs"; +import {runHash} from "../lib/Hash.mjs"; + +/** + * HASSH Server Fingerprint operation + */ +class HASSHServerFingerprint extends Operation { + + /** + * HASSHServerFingerprint constructor + */ + constructor() { + super(); + + this.name = "HASSH Server Fingerprint"; + this.module = "Crypto"; + this.description = "Generates a HASSH fingerprint to help identify SSH servers based on hashing together values from the Server Key Exchange Init message.

Input: A hex stream of the SSH_MSG_KEXINIT packet application layer from Server to Client."; + this.infoURL = "https://engineering.salesforce.com/open-sourcing-hassh-abed3ae5044c"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Input format", + type: "option", + value: ["Hex", "Base64", "Raw"] + }, + { + name: "Output format", + type: "option", + value: ["Hash digest", "HASSH algorithms string", "Full details"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [inputFormat, outputFormat] = args; + + input = Utils.convertToByteArray(input, inputFormat); + const s = new Stream(new Uint8Array(input)); + + // Length + const length = s.readInt(4); + if (s.length !== length + 4) + throw new OperationError("Incorrect packet length."); + + // Padding length + const paddingLength = s.readInt(1); + + // Message code + const messageCode = s.readInt(1); + if (messageCode !== 20) + throw new OperationError("Not a Key Exchange Init."); + + // Cookie + s.moveForwardsBy(16); + + // KEX Algorithms + const kexAlgosLength = s.readInt(4); + const kexAlgos = s.readString(kexAlgosLength); + + // Server Host Key Algorithms + const serverHostKeyAlgosLength = s.readInt(4); + s.moveForwardsBy(serverHostKeyAlgosLength); + + // Encryption Algorithms Client to Server + const encAlgosC2SLength = s.readInt(4); + s.moveForwardsBy(encAlgosC2SLength); + + // Encryption Algorithms Server to Client + const encAlgosS2CLength = s.readInt(4); + const encAlgosS2C = s.readString(encAlgosS2CLength); + + // MAC Algorithms Client to Server + const macAlgosC2SLength = s.readInt(4); + s.moveForwardsBy(macAlgosC2SLength); + + // MAC Algorithms Server to Client + const macAlgosS2CLength = s.readInt(4); + const macAlgosS2C = s.readString(macAlgosS2CLength); + + // Compression Algorithms Client to Server + const compAlgosC2SLength = s.readInt(4); + s.moveForwardsBy(compAlgosC2SLength); + + // Compression Algorithms Server to Client + const compAlgosS2CLength = s.readInt(4); + const compAlgosS2C = s.readString(compAlgosS2CLength); + + // Languages Client to Server + const langsC2SLength = s.readInt(4); + s.moveForwardsBy(langsC2SLength); + + // Languages Server to Client + const langsS2CLength = s.readInt(4); + s.moveForwardsBy(langsS2CLength); + + // First KEX packet follows + s.moveForwardsBy(1); + + // Reserved + s.moveForwardsBy(4); + + // Padding string + s.moveForwardsBy(paddingLength); + + // Output + const hassh = [ + kexAlgos, + encAlgosS2C, + macAlgosS2C, + compAlgosS2C + ]; + const hasshStr = hassh.join(";"); + const hasshHash = runHash("md5", Utils.strToArrayBuffer(hasshStr)); + + switch (outputFormat) { + case "HASSH algorithms string": + return hasshStr; + case "Full details": + return `Hash digest: +${hasshHash} + +Full HASSH algorithms string: +${hasshStr} + +Key Exchange Algorithms: +${kexAlgos} +Encryption Algorithms Server to Client: +${encAlgosS2C} +MAC Algorithms Server to Client: +${macAlgosS2C} +Compression Algorithms Server to Client: +${compAlgosS2C}`; + case "Hash digest": + default: + return hasshHash; + } + } + +} + +export default HASSHServerFingerprint; diff --git a/src/core/operations/JA3Fingerprint.mjs b/src/core/operations/JA3Fingerprint.mjs index 0384c6e4..941e2fcb 100644 --- a/src/core/operations/JA3Fingerprint.mjs +++ b/src/core/operations/JA3Fingerprint.mjs @@ -30,7 +30,7 @@ class JA3Fingerprint extends Operation { this.name = "JA3 Fingerprint"; this.module = "Crypto"; - this.description = "Generates a JA3 fingerprint to help identify TLS clients based on hashing together values from the Client Hello.

Input: A hex stream of the TLS Client Hello application layer."; + this.description = "Generates a JA3 fingerprint to help identify TLS clients based on hashing together values from the Client Hello.

Input: A hex stream of the TLS Client Hello packet application layer."; this.infoURL = "https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967"; this.inputType = "string"; this.outputType = "string"; diff --git a/src/core/operations/JA3SFingerprint.mjs b/src/core/operations/JA3SFingerprint.mjs index b6570501..9c3b88da 100644 --- a/src/core/operations/JA3SFingerprint.mjs +++ b/src/core/operations/JA3SFingerprint.mjs @@ -30,7 +30,7 @@ class JA3SFingerprint extends Operation { this.name = "JA3S Fingerprint"; this.module = "Crypto"; - this.description = "Generates a JA3S fingerprint to help identify TLS servers based on hashing together values from the Server Hello.

Input: A hex stream of the TLS Server Hello record in the application layer."; + this.description = "Generates a JA3S fingerprint to help identify TLS servers based on hashing together values from the Server Hello.

Input: A hex stream of the TLS Server Hello record application layer."; this.infoURL = "https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967"; this.inputType = "string"; this.outputType = "string"; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 8694c443..9add20b9 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -105,6 +105,8 @@ import "./tests/RSA.mjs"; import "./tests/CBOREncode.mjs"; import "./tests/CBORDecode.mjs"; import "./tests/JA3Fingerprint.mjs"; +import "./tests/JA3SFingerprint.mjs"; +import "./tests/HASSH.mjs"; // Cannot test operations that use the File type yet diff --git a/tests/operations/tests/HASSH.mjs b/tests/operations/tests/HASSH.mjs new file mode 100644 index 00000000..2ea2dde5 --- /dev/null +++ b/tests/operations/tests/HASSH.mjs @@ -0,0 +1,33 @@ +/** + * HASSH tests. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "HASSH Client Fingerprint", + input: "000003140814c639665f5425dcb80bf9f0a048380a410000007e6469666669652d68656c6c6d616e2d67726f75702d65786368616e67652d7368613235362c6469666669652d68656c6c6d616e2d67726f75702d65786368616e67652d736861312c6469666669652d68656c6c6d616e2d67726f757031342d736861312c6469666669652d68656c6c6d616e2d67726f7570312d736861310000000f7373682d7273612c7373682d6473730000009d6165733132382d6374722c6165733139322d6374722c6165733235362d6374722c617263666f75723235362c617263666f75723132382c6165733132382d6362632c336465732d6362632c626c6f77666973682d6362632c636173743132382d6362632c6165733139322d6362632c6165733235362d6362632c617263666f75722c72696a6e6461656c2d636263406c797361746f722e6c69752e73650000009d6165733132382d6374722c6165733139322d6374722c6165733235362d6374722c617263666f75723235362c617263666f75723132382c6165733132382d6362632c336465732d6362632c626c6f77666973682d6362632c636173743132382d6362632c6165733139322d6362632c6165733235362d6362632c617263666f75722c72696a6e6461656c2d636263406c797361746f722e6c69752e736500000069686d61632d6d64352c686d61632d736861312c756d61632d3634406f70656e7373682e636f6d2c686d61632d726970656d643136302c686d61632d726970656d64313630406f70656e7373682e636f6d2c686d61632d736861312d39362c686d61632d6d64352d393600000069686d61632d6d64352c686d61632d736861312c756d61632d3634406f70656e7373682e636f6d2c686d61632d726970656d643136302c686d61632d726970656d64313630406f70656e7373682e636f6d2c686d61632d736861312d39362c686d61632d6d64352d39360000001a6e6f6e652c7a6c6962406f70656e7373682e636f6d2c7a6c69620000001a6e6f6e652c7a6c6962406f70656e7373682e636f6d2c7a6c6962000000000000000000000000000000000000000000", + expectedOutput: "21b457a327ce7a2d4fce5ef2c42400bd", + recipeConfig: [ + { + "op": "HASSH Client Fingerprint", + "args": ["Hex", "Hash digest"] + } + ], + }, + { + name: "HASSH Server Fingerprint", + input: "0000027c0b142c7bb93a1da21c9e54f5862e60a5597c000000596469666669652d68656c6c6d616e2d67726f75702d65786368616e67652d736861312c6469666669652d68656c6c6d616e2d67726f757031342d736861312c6469666669652d68656c6c6d616e2d67726f7570312d736861310000000f7373682d7273612c7373682d647373000000876165733132382d6362632c336465732d6362632c626c6f77666973682d6362632c636173743132382d6362632c617263666f75722c6165733139322d6362632c6165733235362d6362632c72696a6e6461656c2d636263406c797361746f722e6c69752e73652c6165733132382d6374722c6165733139322d6374722c6165733235362d637472000000876165733132382d6362632c336465732d6362632c626c6f77666973682d6362632c636173743132382d6362632c617263666f75722c6165733139322d6362632c6165733235362d6362632c72696a6e6461656c2d636263406c797361746f722e6c69752e73652c6165733132382d6374722c6165733139322d6374722c6165733235362d63747200000055686d61632d6d64352c686d61632d736861312c686d61632d726970656d643136302c686d61632d726970656d64313630406f70656e7373682e636f6d2c686d61632d736861312d39362c686d61632d6d64352d393600000055686d61632d6d64352c686d61632d736861312c686d61632d726970656d643136302c686d61632d726970656d64313630406f70656e7373682e636f6d2c686d61632d736861312d39362c686d61632d6d64352d3936000000096e6f6e652c7a6c6962000000096e6f6e652c7a6c6962000000000000000000000000000000000000000000000000", + expectedOutput: "f430cd6761697a6a658ee1d45ed22e49", + recipeConfig: [ + { + "op": "HASSH Server Fingerprint", + "args": ["Hex", "Hash digest"] + } + ], + } +]); diff --git a/tests/operations/tests/JA3SFingerprint.mjs b/tests/operations/tests/JA3SFingerprint.mjs index 047018e8..462d68e9 100644 --- a/tests/operations/tests/JA3SFingerprint.mjs +++ b/tests/operations/tests/JA3SFingerprint.mjs @@ -41,15 +41,17 @@ TestRegister.addTests([ } ], }, - { - name: "JA3S Fingerprint: TLS 1.3", - input: "16030100520200004e7f123ef1609fd3f4fa8668aac5822d500fb0639b22671d0fb7258597355795511bf61301002800280024001d0020ae0e282a3b7a463e71064ecbaf671586e979b0edbebf7a4735c31678c70f660c", - expectedOutput: "986ae432c402479fe7a0c6fbe02164c1", - recipeConfig: [ - { - "op": "JA3S Fingerprint", - "args": ["Hex", "Hash digest"] - } - ], - }, + // This Server Hello was based on draft 18 of the TLS1.3 spec which does not include a Session ID field, leading it to fail. + // The published version of TLS1.3 does require a legacy Session ID field (even if it is empty). + // { + // name: "JA3S Fingerprint: TLS 1.3", + // input: "16030100520200004e7f123ef1609fd3f4fa8668aac5822d500fb0639b22671d0fb7258597355795511bf61301002800280024001d0020ae0e282a3b7a463e71064ecbaf671586e979b0edbebf7a4735c31678c70f660c", + // expectedOutput: "986ae432c402479fe7a0c6fbe02164c1", + // recipeConfig: [ + // { + // "op": "JA3S Fingerprint", + // "args": ["Hex", "Hash digest"] + // } + // ], + // }, ]);