diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index 5d729e323e..d04ccb6b6c 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 0000000000..e507cea117
--- /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 0000000000..f08a418b32
--- /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 0384c6e413..941e2fcbcf 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 b657050133..9c3b88dacc 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 8694c44320..9add20b9bb 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 0000000000..2ea2dde5dd
--- /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 047018e86e..462d68e9ed 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"]
+ // }
+ // ],
+ // },
]);