-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added 'TLS JA3 Fingerprint' operation
- Loading branch information
Showing
4 changed files
with
255 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"] | ||
} | ||
], | ||
}, | ||
]); |