Преглед на файлове

Added 'TLS JA3 Fingerprint' operation

n1474335 преди 4 години
родител
ревизия
9a33498fed
променени са 4 файла, в които са добавени 255 реда и са изтрити 0 реда
  1. 1 0
      src/core/config/Categories.json
  2. 198 0
      src/core/operations/TLSJA3Fingerprint.mjs
  3. 1 0
      tests/operations/index.mjs
  4. 55 0
      tests/operations/tests/TLSJA3Fingerprint.mjs

+ 1 - 0
src/core/config/Categories.json

@@ -193,6 +193,7 @@
             "Protobuf Decode",
             "VarInt Encode",
             "VarInt Decode",
+            "TLS JA3 Fingerprint",
             "Format MAC addresses",
             "Change IP format",
             "Group IP addresses",

+ 198 - 0
src/core/operations/TLSJA3Fingerprint.mjs

@@ -0,0 +1,198 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2021
+ * @license Apache-2.0
+ */
+
+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";
+
+/**
+ * TLS JA3 Fingerprint operation
+ */
+class TLSJA3Fingerprint extends Operation {
+
+    /**
+     * TLSJA3Fingerprint constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "TLS 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.<br><br>Input: A hex stream of the TLS Client Hello 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", "JA3 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 !== 1)
+            throw new OperationError("Not a Client Hello.");
+
+        // Handshake length
+        const handshakeLength = s.readInt(3);
+        if (s.length !== handshakeLength + 9)
+            throw new OperationError("Not enough data in Client Hello.");
+
+        // Hello version
+        const helloVersion = s.readInt(2);
+
+        // Random
+        s.moveForwardsBy(32);
+
+        // Session ID
+        const sessionIDLength = s.readInt(1);
+        s.moveForwardsBy(sessionIDLength);
+
+        // Cipher suites
+        const cipherSuitesLength = s.readInt(2);
+        const cipherSuites = s.getBytes(cipherSuitesLength);
+        const cs = new Stream(cipherSuites);
+        const cipherSegment = parseJA3Segment(cs, 2);
+
+        // Compression Methods
+        const compressionMethodsLength = s.readInt(1);
+        s.moveForwardsBy(compressionMethodsLength);
+
+        // Extensions
+        const extensionsLength = s.readInt(2);
+        const extensions = s.getBytes(extensionsLength);
+        const es = new Stream(extensions);
+        let ecsLen, ecs, ellipticCurves = "", ellipticCurvePointFormats = "";
+        const exts = [];
+        while (es.hasMore()) {
+            const type = es.readInt(2);
+            const length = es.readInt(2);
+            switch (type) {
+                case 0x0a: // Elliptic curves
+                    ecsLen = es.readInt(2);
+                    ecs = new Stream(es.getBytes(ecsLen));
+                    ellipticCurves = parseJA3Segment(ecs, 2);
+                    break;
+                case 0x0b: // Elliptic curve point formats
+                    ecsLen = es.readInt(1);
+                    ecs = new Stream(es.getBytes(ecsLen));
+                    ellipticCurvePointFormats = parseJA3Segment(ecs, 1);
+                    break;
+                default:
+                    es.moveForwardsBy(length);
+            }
+            if (!GREASE_CIPHERSUITES.includes(type))
+                exts.push(type);
+        }
+
+        // Output
+        const ja3 = [
+            helloVersion.toString(),
+            cipherSegment,
+            exts.join("-"),
+            ellipticCurves,
+            ellipticCurvePointFormats
+        ];
+        const ja3Str = ja3.join(",");
+        const ja3Hash = runHash("md5", Utils.strToArrayBuffer(ja3Str));
+
+        switch (outputFormat) {
+            case "JA3 string":
+                return ja3Str;
+            case "Full details":
+                return `Hash digest:
+${ja3Hash}
+
+Full JA3 string:
+${ja3Str}
+
+TLS Version:
+${helloVersion.toString()}
+Cipher Suites:
+${cipherSegment}
+Extensions:
+${exts.join("-")}
+Elliptic Curves:
+${ellipticCurves}
+Elliptic Curve Point Formats:
+${ellipticCurvePointFormats}`;
+            case "Hash digest":
+            default:
+                return ja3Hash;
+        }
+    }
+
+}
+
+/**
+ * Parses a JA3 segment, returning a "-" separated list
+ *
+ * @param {Stream} stream
+ * @returns {string}
+ */
+function parseJA3Segment(stream, size=2) {
+    const segment = [];
+    while (stream.hasMore()) {
+        const element = stream.readInt(size);
+        if (!GREASE_CIPHERSUITES.includes(element))
+            segment.push(element);
+    }
+    return segment.join("-");
+}
+
+const GREASE_CIPHERSUITES = [
+    0x0a0a,
+    0x1a1a,
+    0x2a2a,
+    0x3a3a,
+    0x4a4a,
+    0x5a5a,
+    0x6a6a,
+    0x7a7a,
+    0x8a8a,
+    0x9a9a,
+    0xaaaa,
+    0xbaba,
+    0xcaca,
+    0xdada,
+    0xeaea,
+    0xfafa
+];
+
+export default TLSJA3Fingerprint;

+ 1 - 0
tests/operations/index.mjs

@@ -104,6 +104,7 @@ import "./tests/Unicode.mjs";
 import "./tests/RSA.mjs";
 import "./tests/CBOREncode.mjs";
 import "./tests/CBORDecode.mjs";
+import "./tests/TLSJA3Fingerprint.mjs";
 
 
 // Cannot test operations that use the File type yet

+ 55 - 0
tests/operations/tests/TLSJA3Fingerprint.mjs

@@ -0,0 +1,55 @@
+/**
+ * TLSJA3Fingerprint tests.
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2021
+ * @license Apache-2.0
+ */
+import TestRegister from "../../lib/TestRegister.mjs";
+
+TestRegister.addTests([
+    {
+        name: "TLS JA3 Fingerprint: TLS 1.0",
+        input: "16030100a4010000a00301543dd2dd48f517ca9a93b1e599f019fdece704a23e86c1dcac588427abbaddf200005cc014c00a0039003800880087c00fc00500350084c012c00800160013c00dc003000ac013c00900330032009a009900450044c00ec004002f009600410007c011c007c00cc002000500040015001200090014001100080006000300ff0100001b000b000403000102000a000600040018001700230000000f000101",
+        expectedOutput: "503053a0c5b2bd9b9334bf7f3d3b8852",
+        recipeConfig: [
+            {
+                "op": "TLS JA3 Fingerprint",
+                "args": ["Hex", "Hash digest"]
+            }
+        ],
+    },
+    {
+        name: "TLS JA3 Fingerprint: TLS 1.1",
+        input: "16030100a4010000a00302543dd2ed907e47d0086f34bee2c52dd6ccd8de63ba9387f5e810b09d9d49b38000005cc014c00a0039003800880087c00fc00500350084c012c00800160013c00dc003000ac013c00900330032009a009900450044c00ec004002f009600410007c011c007c00cc002000500040015001200090014001100080006000300ff0100001b000b000403000102000a000600040018001700230000000f000101",
+        expectedOutput: "a314eb64cee6cb832aaaa372c8295bab",
+        recipeConfig: [
+            {
+                "op": "TLS JA3 Fingerprint",
+                "args": ["Hex", "Hash digest"]
+            }
+        ],
+    },
+    {
+        name: "TLS JA3 Fingerprint: TLS 1.2",
+        input: "1603010102010000fe0303543dd3283283692d85f9416b5ccc65d2aafca45c6530b3c6eafbf6d371b6a015000094c030c02cc028c024c014c00a00a3009f006b006a0039003800880087c032c02ec02ac026c00fc005009d003d00350084c012c00800160013c00dc003000ac02fc02bc027c023c013c00900a2009e0067004000330032009a009900450044c031c02dc029c025c00ec004009c003c002f009600410007c011c007c00cc002000500040015001200090014001100080006000300ff01000041000b000403000102000a000600040018001700230000000d002200200601060206030501050205030401040204030301030203030201020202030101000f000101",
+        expectedOutput: "c1a36e1a870786cc75edddc0009eaf3a",
+        recipeConfig: [
+            {
+                "op": "TLS JA3 Fingerprint",
+                "args": ["Hex", "Hash digest"]
+            }
+        ],
+    },
+    {
+        name: "TLS JA3 Fingerprint: TLS 1.3",
+        input: "1603010200010001fc03034355d402c132771a9386b6e9994ae37069e0621af504c26673b1343843c21d8d0000264a4a130113021303c02bc02fc02cc030cca9cca8cc14cc13c013c014009c009d002f0035000a010001addada0000ff01000100000000180016000013626c6f672e636c6f7564666c6172652e636f6d0017000000230000000d00140012040308040401050308050501080606010201000500050100000000001200000010000e000c02683208687474702f312e3175500000000b000201000028002b00295a5a000100001d0020cf78b9167af054b922a96752b43973107b2a57766357dd288b2b42ab5df30e08002d00020101002b000b0acaca7f12030303020301000a000a00085a5a001d001700180a0a000100001500e4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+        expectedOutput: "4826a90ec2daf4f7b4b64cc1c8bd343b",
+        recipeConfig: [
+            {
+                "op": "TLS JA3 Fingerprint",
+                "args": ["Hex", "Hash digest"]
+            }
+        ],
+    },
+]);