Reworked nDPI fingerprint to avoid affecting JA4 in case of configuration nDPI options set (#3094)

This commit is contained in:
Luca Deri 2026-01-18 22:03:50 +01:00 committed by GitHub
parent 36b79954d9
commit b28bbd994a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 59 additions and 39 deletions

View file

@ -1739,7 +1739,7 @@ struct ndpi_flow_struct {
struct {
char *server_names, *advertised_alpns, *negotiated_alpn, *tls_supported_versions, *issuerDN, *subjectDN;
u_int32_t notBefore, notAfter;
char ja3_server[33], ja4_client[37], *ja4_client_raw;
char ja3_server[33], ja4_client[37], ja4_ndpi_client[37], *ja4_client_raw;
u_int16_t server_cipher;
u_int8_t sha1_certificate_fingerprint[20];
u_int8_t client_hello_processed:1, ch_direction:1, subprotocol_detected:1,

View file

@ -181,8 +181,9 @@ static char* ndpi_compute_tls_blocks_flow_fingerprint(struct ndpi_flow_struct *f
break;
default:
ret = snprintf(&fp_buf[idx], fp_buf_len-idx-1, "%s%u",
ret = snprintf(&fp_buf[idx], fp_buf_len-idx-1, "%s%s%u",
(i > 0) ? "," : "",
(flow->l4.tcp.tls.tls_blocks[i].len > 0) ? "+" : "-",
flow->l4.tcp.tls.tls_blocks[i].block_type);
}
@ -226,7 +227,7 @@ char* ndpi_compute_ndpi_flow_fingerprint(struct ndpi_detection_module_struct *nd
* no fingerprint for mid-flows
TODO: is that what we really want?
*/
(flow->tcp.fingerprint || flow->protos.tls_quic.ja4_client[0] != '\0')) {
(flow->tcp.fingerprint || flow->protos.tls_quic.ja4_ndpi_client[0] != '\0')) {
char *l4_fp = "no_l4_fp";
char *l7_pf = "no_app_fp_cli";
char *l7_pf_tls_blocks = "";
@ -240,8 +241,8 @@ char* ndpi_compute_ndpi_flow_fingerprint(struct ndpi_detection_module_struct *nd
&& (flow->tcp.fingerprint != NULL))
l4_fp = flow->tcp.fingerprint;
if(flow->protos.tls_quic.ja4_client[0] != '\0')
l7_pf = flow->protos.tls_quic.ja4_client;
if(flow->protos.tls_quic.ja4_ndpi_client[0] != '\0')
l7_pf = flow->protos.tls_quic.ja4_ndpi_client;
if(ndpi_str->cfg.tls_max_num_blocks_to_analyze > 0)
l7_pf_tls_blocks = ndpi_compute_tls_blocks_flow_fingerprint(flow,

View file

@ -2114,17 +2114,37 @@ static bool is_grease_version(u_int16_t version) {
/* **************************************** */
bool skipTLSextension(struct ndpi_detection_module_struct *ndpi_struct,
u_int16_t extension_id) {
if((extension_id == 0x0 /* SNI */) && ndpi_struct->cfg.tls_ndpifp_ignore_sni_extension)
return(true);
if(ndpi_struct->cfg.tls_ja_ignore_ephemeral_extensions) {
switch(extension_id) {
case 0x23: /* session ticket - RFC 9149 */
case 0x29: /* pre-shared key - RFC 8446 */
case 0x15: /* padding - RFC 7685 */
return(true);
}
}
return(false);
}
/* **************************************** */
static void ndpi_compute_ja4(struct ndpi_detection_module_struct *ndpi_struct,
struct ndpi_flow_struct *flow,
u_int32_t quic_version,
union ndpi_ja_info *ja) {
u_int8_t tmp_str[JA_STR_LEN];
u_int tmp_str_len, num_extn;
u_int8_t tmp_str[JA_STR_LEN], tmp_ndpi_str[512];
u_int tmp_str_len, tmp_ndpi_str_len = 0, num_extn, num_ndpi_extn;
u_int8_t sha_hash[NDPI_SHA256_BLOCK_SIZE];
u_int16_t ja_str_len, i;
u_int16_t ja_str_len, i, ja_offset;
int rc;
u_int16_t tls_handshake_version = ja->client.tls_handshake_version;
char * const ja_str = &flow->protos.tls_quic.ja4_client[0];
char * const ja_ndpi_str = &flow->protos.tls_quic.ja4_ndpi_client[0];
const u_int16_t ja_max_len = sizeof(flow->protos.tls_quic.ja4_client);
bool is_dtls = ((flow->l4_proto == IPPROTO_UDP) && (quic_version == 0)) || flow->stun.maybe_dtls;
int ja4_r_len = 0;
@ -2280,7 +2300,7 @@ static void ndpi_compute_ja4(struct ndpi_detection_module_struct *ndpi_struct,
#endif
tmp_str_len = 0;
for(i=0, num_extn = 0; i<ja->client.num_tls_extensions; i++) {
for(i=0, num_extn = num_ndpi_extn = 0; i<ja->client.num_tls_extensions; i++) {
if((ja->client.tls_extension[i] > 0) && (ja->client.tls_extension[i] != 0x10 /* ALPN extension */)) {
#ifdef JA4R_DECIMAL
rc = snprintf(&ja4_r[ja4_r_len], sizeof(ja4_r)-ja4_r_len, "%s%u", (num_extn > 0) ? "," : "", ja->client.tls_extension[i]);
@ -2289,17 +2309,28 @@ static void ndpi_compute_ja4(struct ndpi_detection_module_struct *ndpi_struct,
rc = ndpi_snprintf((char *)&tmp_str[tmp_str_len], JA_STR_LEN-tmp_str_len, "%s%04x",
(num_extn > 0) ? "," : "", ja->client.tls_extension[i]);
if((rc > 0) && (tmp_str_len + rc < JA_STR_LEN)) tmp_str_len += rc; else break;
if((rc > 0) && (tmp_str_len + rc < JA_STR_LEN)) tmp_str_len += rc; else break;
num_extn++;
if(!skipTLSextension(ndpi_struct, ja->client.tls_extension[i])) {
rc = ndpi_snprintf((char *)&tmp_ndpi_str[tmp_ndpi_str_len], sizeof(tmp_ndpi_str)-tmp_ndpi_str_len, "%s%04x",
(num_ndpi_extn > 0) ? "," : "", ja->client.tls_extension[i]);
if((rc > 0) && (tmp_ndpi_str_len + rc < sizeof(tmp_ndpi_str))) tmp_ndpi_str_len += rc; else break;
num_ndpi_extn++;
}
}
}
for(i=0; i<ja->client.num_signature_algorithms; i++) {
rc = ndpi_snprintf((char *)&tmp_str[tmp_str_len], JA_STR_LEN-tmp_str_len, "%s%04x",
(i > 0) ? "," : "_", ja->client.signature_algorithm[i]);
if((rc > 0) && (tmp_str_len + rc < JA_STR_LEN)) tmp_str_len += rc; else break;
}
rc = ndpi_snprintf((char *)&tmp_ndpi_str[tmp_ndpi_str_len], sizeof(tmp_ndpi_str)-tmp_ndpi_str_len, "%s%04x",
(i > 0) ? "," : "_", ja->client.signature_algorithm[i]);
if((rc > 0) && (tmp_ndpi_str_len + rc < sizeof(tmp_ndpi_str))) tmp_ndpi_str_len += rc; else break;
}
#ifdef DEBUG_JA
printf("[EXTN] %s [len: %u]\n", tmp_str, tmp_str_len);
#endif
@ -2318,19 +2349,27 @@ static void ndpi_compute_ja4(struct ndpi_detection_module_struct *ndpi_struct,
#endif
}
if(ja->client.num_tls_extensions > 0) {
ndpi_sha256(tmp_str, tmp_str_len, sha_hash);
} else {
memset(sha_hash, '\0', 6);
}
if(ja->client.num_tls_extensions > 0) ndpi_sha256(tmp_str, tmp_str_len, sha_hash); else memset(sha_hash, '\0', 6);
ja_offset = ja_str_len;
rc = ndpi_snprintf(&ja_str[ja_str_len], ja_max_len - ja_str_len,
"%02x%02x%02x%02x%02x%02x",
sha_hash[0], sha_hash[1], sha_hash[2],
sha_hash[3], sha_hash[4], sha_hash[5]);
if((rc > 0) && (ja_str_len + rc < JA_STR_LEN)) ja_str_len += rc;
ja_str[36] = 0;
/* nDPI */
if(ja->client.num_tls_extensions > 0) ndpi_sha256(tmp_ndpi_str, tmp_ndpi_str_len, sha_hash); else memset(sha_hash, '\0', 6);
ja_str_len = ja_offset;
strncpy(ja_ndpi_str, ja_str, ja_str_len);
rc = ndpi_snprintf(&ja_ndpi_str[ja_str_len], ja_max_len - ja_str_len,
"%02x%02x%02x%02x%02x%02x",
sha_hash[0], sha_hash[1], sha_hash[2],
sha_hash[3], sha_hash[4], sha_hash[5]);
if((rc > 0) && (ja_str_len + rc < JA_STR_LEN)) ja_str_len += rc;
ja_ndpi_str[36] = 0;
#ifdef DEBUG_JA
printf("[JA4] %s [len: %lu]\n", ja_str, strlen(ja_str));
@ -2339,25 +2378,6 @@ static void ndpi_compute_ja4(struct ndpi_detection_module_struct *ndpi_struct,
/* **************************************** */
bool skipTLSextension(struct ndpi_detection_module_struct *ndpi_struct,
u_int16_t extension_id) {
if((extension_id == 0x0 /* SNI */) && ndpi_struct->cfg.tls_ndpifp_ignore_sni_extension)
return(true);
if(ndpi_struct->cfg.tls_ja_ignore_ephemeral_extensions) {
switch(extension_id) {
case 0x23: /* session ticket - RFC 9149 */
case 0x29: /* pre-shared key - RFC 8446 */
case 0x15: /* padding - RFC 7685 */
return(true);
}
}
return(false);
}
/* **************************************** */
int processClientServerHello(struct ndpi_detection_module_struct *ndpi_struct,
struct ndpi_flow_struct *flow, u_int32_t quic_version) {
struct ndpi_packet_struct *packet = &ndpi_struct->packet;
@ -2877,8 +2897,7 @@ int processClientServerHello(struct ndpi_detection_module_struct *ndpi_struct,
flow->l4.tcp.tls.tls_blocks[flow->l4.tcp.tls.num_tls_blocks-1].len -= extension_len + 4 /* id + len */;
}
if(!skipTLSextension(ndpi_struct, extension_id))
ja.client.tls_extension[ja.client.num_tls_extensions++] = extension_id;
ja.client.tls_extension[ja.client.num_tls_extensions++] = extension_id;
} else {
invalid_ja = 1;
#ifdef DEBUG_TLS