This commit is contained in:
0xff1ce 2025-04-09 16:18:30 -07:00 committed by GitHub
commit 68a87a77ec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 88 additions and 0 deletions

View file

@ -261,6 +261,7 @@
"JA3S Fingerprint", "JA3S Fingerprint",
"JA4 Fingerprint", "JA4 Fingerprint",
"JA4Server Fingerprint", "JA4Server Fingerprint",
"JA4 Fingerprint Summary",
"HASSH Client Fingerprint", "HASSH Client Fingerprint",
"HASSH Server Fingerprint", "HASSH Server Fingerprint",
"Format MAC addresses", "Format MAC addresses",

View file

@ -0,0 +1,87 @@
/**
* @author 0xff1ce [github.com/0xff1ce]
* @copyright Crown Copyright 2024
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
/**
* JA4 Fingerprint Summary operation
*/
class JA4FingerprintSummary extends Operation {
/**
* JA4FingerprintSummary constructor
*/
constructor() {
super();
this.name = "JA4 Fingerprint Summary";
this.module = "Crypto";
this.description = "Generates a JA4 fingerprint summary to explain the hash output of the JA4 Fingerprint recipe.<br><br>Input: A standard JA4/JA4Server Fingerprint";
this.infoURL = "https://blog.foxio.io/ja4%2B-network-fingerprinting";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Fingerprint format",
type: "option",
value: ["JA4", "JA4Server"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [inputFormat] = args;
let retString = `Fingerprint summary of: ${input}\n+------------------------------------------------------------+`;
// Define format validation logic
const isValidJA4 = (input) => /[a-z-0-9]{10}_[a-z-0-9]{12}_[a-z-0-9]{12}/.test(input);
const isValidJA4Server = (input) => /[a-z-0-9]{7}_[a-z-0-9]{4}_[a-z-0-9]{12}/.test(input);
// Validate the input based on inputFormat
if (inputFormat === "JA4" && !isValidJA4(input)) {
return `Error: The input does not match the expected JA4 format. Please check your input: ${input}`;
}
if (inputFormat === "JA4Server" && !isValidJA4Server(input)) {
return `Error: The input does not match the expected JA4Server format. Please check your input: ${input}`;
}
const protocolMap = { t: "TCP", q: "QUIC" };
const tlsMap = {
"13": "1.3", "12": "1.2", "11": "1.1", "10": "1.0",
"s3": "SSL 3.0", "s2": "SSL 2.0", "s1": "SSL 1.0"
};
const alpnMap = {
"00": "No ALPN Chosen", "dt": "DNS-over-TLS",
"h1": "HTTP/1.1", "h2": "HTTP/2"
};
const getProtocol = (input) => `\nProtocol: ${protocolMap[input[0]] || "Unknown"}`;
const getTLSVersion = (input, start = 1, end = 3) => `\nTLS Version: ${tlsMap[input.slice(start, end)] || input.slice(start, end)}`;
const getALPN = (input, start, end) => `\nALPN Chosen: ${alpnMap[input.slice(start, end)] || input.slice(start, end)}`;
if (inputFormat === "JA4") {
const sniMap = { d: "Yes (to domain)", i: "No (to IP)" };
retString += getProtocol(input);
retString += getTLSVersion(input);
retString += `\nSNI Extension Present: ${sniMap[input[3]] || input[3] + " (Invalid Symbol)"}`;
retString += `\nNumber of Cipher Suites: ${input.slice(4, 6)}`;
retString += `\nNumber of Extensions: ${input.slice(6, 8)}`;
retString += getALPN(input, 8, 10);
retString += `\nTruncated SHA256 hash of the Cipher Suites, sorted: ${input.slice(11, 23)}`;
retString += `\nTruncated SHA256 hash of the Extensions, sorted + Signature Algorithms, in the order they appear: ${input.slice(24)}`;
} else if (inputFormat === "JA4Server") {
retString += getProtocol(input);
retString += getTLSVersion(input);
retString += `\nNumber of Extensions: ${input.slice(3, 5)}`;
retString += getALPN(input, 5, 7);
retString += `\nCipher Suite Chosen: ${input.slice(8, 12)}`;
retString += `\nTruncated SHA256 hash of the Extensions, in the order they appear: ${input.slice(13)}`;
}
return retString;
}
}
export default JA4FingerprintSummary;