diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index ff472caf..5d729e32 100755
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -193,7 +193,8 @@
"Protobuf Decode",
"VarInt Encode",
"VarInt Decode",
- "TLS JA3 Fingerprint",
+ "JA3 Fingerprint",
+ "JA3S Fingerprint",
"Format MAC addresses",
"Change IP format",
"Group IP addresses",
diff --git a/src/core/operations/TLSJA3Fingerprint.mjs b/src/core/operations/JA3Fingerprint.mjs
similarity index 96%
rename from src/core/operations/TLSJA3Fingerprint.mjs
rename to src/core/operations/JA3Fingerprint.mjs
index d906d032..0384c6e4 100644
--- a/src/core/operations/TLSJA3Fingerprint.mjs
+++ b/src/core/operations/JA3Fingerprint.mjs
@@ -18,17 +18,17 @@ import Stream from "../lib/Stream.mjs";
import {runHash} from "../lib/Hash.mjs";
/**
- * TLS JA3 Fingerprint operation
+ * JA3 Fingerprint operation
*/
-class TLSJA3Fingerprint extends Operation {
+class JA3Fingerprint extends Operation {
/**
- * TLSJA3Fingerprint constructor
+ * JA3Fingerprint constructor
*/
constructor() {
super();
- this.name = "TLS JA3 Fingerprint";
+ 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.infoURL = "https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967";
@@ -202,4 +202,4 @@ const GREASE_CIPHERSUITES = [
0xfafa
];
-export default TLSJA3Fingerprint;
+export default JA3Fingerprint;
diff --git a/src/core/operations/JA3SFingerprint.mjs b/src/core/operations/JA3SFingerprint.mjs
new file mode 100644
index 00000000..b6570501
--- /dev/null
+++ b/src/core/operations/JA3SFingerprint.mjs
@@ -0,0 +1,145 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2021
+ * @license Apache-2.0
+ *
+ * JA3S created by Salesforce
+ * 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";
+
+/**
+ * JA3S Fingerprint operation
+ */
+class JA3SFingerprint extends Operation {
+
+ /**
+ * JA3SFingerprint constructor
+ */
+ constructor() {
+ super();
+
+ 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.infoURL = "https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967";
+ 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", "JA3S 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));
+
+ const handshake = s.readInt(1);
+ if (handshake !== 0x16)
+ throw new OperationError("Not handshake data.");
+
+ // Version
+ s.moveForwardsBy(2);
+
+ // Length
+ const length = s.readInt(2);
+ if (s.length !== length + 5)
+ throw new OperationError("Incorrect handshake length.");
+
+ // Handshake type
+ const handshakeType = s.readInt(1);
+ if (handshakeType !== 2)
+ throw new OperationError("Not a Server Hello.");
+
+ // Handshake length
+ const handshakeLength = s.readInt(3);
+ if (s.length !== handshakeLength + 9)
+ throw new OperationError("Not enough data in Server Hello.");
+
+ // Hello version
+ const helloVersion = s.readInt(2);
+
+ // Random
+ s.moveForwardsBy(32);
+
+ // Session ID
+ const sessionIDLength = s.readInt(1);
+ s.moveForwardsBy(sessionIDLength);
+
+ // Cipher suite
+ const cipherSuite = s.readInt(2);
+
+ // Compression Method
+ s.moveForwardsBy(1);
+
+ // Extensions
+ const extensionsLength = s.readInt(2);
+ const extensions = s.getBytes(extensionsLength);
+ const es = new Stream(extensions);
+ const exts = [];
+ while (es.hasMore()) {
+ const type = es.readInt(2);
+ const length = es.readInt(2);
+ es.moveForwardsBy(length);
+ exts.push(type);
+ }
+
+ // Output
+ const ja3s = [
+ helloVersion.toString(),
+ cipherSuite,
+ exts.join("-")
+ ];
+ const ja3sStr = ja3s.join(",");
+ const ja3sHash = runHash("md5", Utils.strToArrayBuffer(ja3sStr));
+
+ switch (outputFormat) {
+ case "JA3S string":
+ return ja3sStr;
+ case "Full details":
+ return `Hash digest:
+${ja3sHash}
+
+Full JA3S string:
+${ja3sStr}
+
+TLS Version:
+${helloVersion.toString()}
+Cipher Suite:
+${cipherSuite}
+Extensions:
+${exts.join("-")}`;
+ case "Hash digest":
+ default:
+ return ja3sHash;
+ }
+ }
+
+}
+
+export default JA3SFingerprint;
diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs
index fdce5131..8694c443 100644
--- a/tests/operations/index.mjs
+++ b/tests/operations/index.mjs
@@ -104,7 +104,7 @@ import "./tests/Unicode.mjs";
import "./tests/RSA.mjs";
import "./tests/CBOREncode.mjs";
import "./tests/CBORDecode.mjs";
-import "./tests/TLSJA3Fingerprint.mjs";
+import "./tests/JA3Fingerprint.mjs";
// Cannot test operations that use the File type yet
diff --git a/tests/operations/tests/TLSJA3Fingerprint.mjs b/tests/operations/tests/JA3Fingerprint.mjs
similarity index 89%
rename from tests/operations/tests/TLSJA3Fingerprint.mjs
rename to tests/operations/tests/JA3Fingerprint.mjs
index 4f1534fb..618dc73d 100644
--- a/tests/operations/tests/TLSJA3Fingerprint.mjs
+++ b/tests/operations/tests/JA3Fingerprint.mjs
@@ -1,5 +1,5 @@
/**
- * TLSJA3Fingerprint tests.
+ * JA3Fingerprint tests.
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2021
@@ -9,45 +9,45 @@ import TestRegister from "../../lib/TestRegister.mjs";
TestRegister.addTests([
{
- name: "TLS JA3 Fingerprint: TLS 1.0",
+ name: "JA3 Fingerprint: TLS 1.0",
input: "16030100a4010000a00301543dd2dd48f517ca9a93b1e599f019fdece704a23e86c1dcac588427abbaddf200005cc014c00a0039003800880087c00fc00500350084c012c00800160013c00dc003000ac013c00900330032009a009900450044c00ec004002f009600410007c011c007c00cc002000500040015001200090014001100080006000300ff0100001b000b000403000102000a000600040018001700230000000f000101",
expectedOutput: "503053a0c5b2bd9b9334bf7f3d3b8852",
recipeConfig: [
{
- "op": "TLS JA3 Fingerprint",
+ "op": "JA3 Fingerprint",
"args": ["Hex", "Hash digest"]
}
],
},
{
- name: "TLS JA3 Fingerprint: TLS 1.1",
+ name: "JA3 Fingerprint: TLS 1.1",
input: "16030100a4010000a00302543dd2ed907e47d0086f34bee2c52dd6ccd8de63ba9387f5e810b09d9d49b38000005cc014c00a0039003800880087c00fc00500350084c012c00800160013c00dc003000ac013c00900330032009a009900450044c00ec004002f009600410007c011c007c00cc002000500040015001200090014001100080006000300ff0100001b000b000403000102000a000600040018001700230000000f000101",
expectedOutput: "a314eb64cee6cb832aaaa372c8295bab",
recipeConfig: [
{
- "op": "TLS JA3 Fingerprint",
+ "op": "JA3 Fingerprint",
"args": ["Hex", "Hash digest"]
}
],
},
{
- name: "TLS JA3 Fingerprint: TLS 1.2",
+ name: "JA3 Fingerprint: TLS 1.2",
input: "1603010102010000fe0303543dd3283283692d85f9416b5ccc65d2aafca45c6530b3c6eafbf6d371b6a015000094c030c02cc028c024c014c00a00a3009f006b006a0039003800880087c032c02ec02ac026c00fc005009d003d00350084c012c00800160013c00dc003000ac02fc02bc027c023c013c00900a2009e0067004000330032009a009900450044c031c02dc029c025c00ec004009c003c002f009600410007c011c007c00cc002000500040015001200090014001100080006000300ff01000041000b000403000102000a000600040018001700230000000d002200200601060206030501050205030401040204030301030203030201020202030101000f000101",
expectedOutput: "c1a36e1a870786cc75edddc0009eaf3a",
recipeConfig: [
{
- "op": "TLS JA3 Fingerprint",
+ "op": "JA3 Fingerprint",
"args": ["Hex", "Hash digest"]
}
],
},
{
- name: "TLS JA3 Fingerprint: TLS 1.3",
+ name: "JA3 Fingerprint: TLS 1.3",
input: "1603010200010001fc03034355d402c132771a9386b6e9994ae37069e0621af504c26673b1343843c21d8d0000264a4a130113021303c02bc02fc02cc030cca9cca8cc14cc13c013c014009c009d002f0035000a010001addada0000ff01000100000000180016000013626c6f672e636c6f7564666c6172652e636f6d0017000000230000000d00140012040308040401050308050501080606010201000500050100000000001200000010000e000c02683208687474702f312e3175500000000b000201000028002b00295a5a000100001d0020cf78b9167af054b922a96752b43973107b2a57766357dd288b2b42ab5df30e08002d00020101002b000b0acaca7f12030303020301000a000a00085a5a001d001700180a0a000100001500e4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
expectedOutput: "4826a90ec2daf4f7b4b64cc1c8bd343b",
recipeConfig: [
{
- "op": "TLS JA3 Fingerprint",
+ "op": "JA3 Fingerprint",
"args": ["Hex", "Hash digest"]
}
],
diff --git a/tests/operations/tests/JA3SFingerprint.mjs b/tests/operations/tests/JA3SFingerprint.mjs
new file mode 100644
index 00000000..047018e8
--- /dev/null
+++ b/tests/operations/tests/JA3SFingerprint.mjs
@@ -0,0 +1,55 @@
+/**
+ * JA3SFingerprint tests.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2021
+ * @license Apache-2.0
+ */
+import TestRegister from "../../lib/TestRegister.mjs";
+
+TestRegister.addTests([
+ {
+ name: "JA3S Fingerprint: TLS 1.0",
+ input: "160301003d020000390301543dd2ddedbfe33895bd6bc676a3fa6b9fe5773a6e04d5476d1af3bcbc1dcbbb00c011000011ff01000100000b00040300010200230000",
+ expectedOutput: "bed95e1b525d2f41db3a6d68fac5b566",
+ recipeConfig: [
+ {
+ "op": "JA3S Fingerprint",
+ "args": ["Hex", "Hash digest"]
+ }
+ ],
+ },
+ {
+ name: "JA3S Fingerprint: TLS 1.1",
+ input: "160302003d020000390302543dd2ed88131999a0120d36c14a4139671d75aae3d7d7779081d3cf7dd7725a00c013000011ff01000100000b00040300010200230000",
+ expectedOutput: "130fac2dc19b142500acb0abc63b6379",
+ recipeConfig: [
+ {
+ "op": "JA3S Fingerprint",
+ "args": ["Hex", "Hash digest"]
+ }
+ ],
+ },
+ {
+ name: "JA3S Fingerprint: TLS 1.2",
+ input: "160303003d020000390303543dd328b38b445686739d58fab733fa23838f575e0e5ad9a1b9baace6cc3b4100c02f000011ff01000100000b00040300010200230000",
+ expectedOutput: "ccc514751b175866924439bdbb5bba34",
+ recipeConfig: [
+ {
+ "op": "JA3S Fingerprint",
+ "args": ["Hex", "Hash digest"]
+ }
+ ],
+ },
+ {
+ name: "JA3S Fingerprint: TLS 1.3",
+ input: "16030100520200004e7f123ef1609fd3f4fa8668aac5822d500fb0639b22671d0fb7258597355795511bf61301002800280024001d0020ae0e282a3b7a463e71064ecbaf671586e979b0edbebf7a4735c31678c70f660c",
+ expectedOutput: "986ae432c402479fe7a0c6fbe02164c1",
+ recipeConfig: [
+ {
+ "op": "JA3S Fingerprint",
+ "args": ["Hex", "Hash digest"]
+ }
+ ],
+ },
+]);