diff --git a/src/include/ndpi_private.h b/src/include/ndpi_private.h index 039409bf6e0..18926b9ea12 100644 --- a/src/include/ndpi_private.h +++ b/src/include/ndpi_private.h @@ -498,6 +498,7 @@ struct ndpi_detection_module_struct { u_int16_t max_payload_track_len; ndpi_str_hash *public_domain_suffixes, *ja4_custom_protos, *ndpifp_custom_protos; + ndpi_tls_cert_hash_match_dynamic *dynamic_tls_cert_hash_list; struct ndpi_address_cache *address_cache; struct { ndpi_filter *cache, *cache_shadow; diff --git a/src/include/ndpi_typedefs.h b/src/include/ndpi_typedefs.h index a56f92df25e..ec5145af228 100644 --- a/src/include/ndpi_typedefs.h +++ b/src/include/ndpi_typedefs.h @@ -1634,6 +1634,18 @@ struct ndpi_ipsec_details { struct ndpi_ipsec_proposal proposal[2]; }; +typedef struct ndpi_tls_cert_name_match_dynamic { + char *cert_pattern; + u_int16_t protocol_id; + struct ndpi_tls_cert_name_match_dynamic *next; +} ndpi_tls_cert_name_match_dynamic; + +typedef struct ndpi_tls_cert_hash_match_dynamic { + char *cert_hash; + u_int16_t protocol_id; + struct ndpi_tls_cert_hash_match_dynamic *next; +} ndpi_tls_cert_hash_match_dynamic; + struct ndpi_flow_struct { u_int16_t detected_protocol_stack[NDPI_PROTOCOL_SIZE]; struct ndpi_proto_stack protocol_stack; diff --git a/src/lib/ndpi_main.c b/src/lib/ndpi_main.c index 2a1a14e7998..68b191e1723 100644 --- a/src/lib/ndpi_main.c +++ b/src/lib/ndpi_main.c @@ -3765,7 +3765,34 @@ static int ndpi_add_ndpifp_subprotocol(struct ndpi_detection_module_struct *ndpi return(ndpi_hash_add_entry(&ndpi_str->ndpifp_custom_protos, ndpifp, ndpifp_len, protocol_id, (void*)blocks)); } +static int ndpi_add_tls_cert_hash_subprotocol(struct ndpi_detection_module_struct *ndpi_str, + char *cert_hash, + u_int16_t protocol_id) { + ndpi_tls_cert_hash_match_dynamic *new_rule; + if(!cert_hash || cert_hash[0] == '\0') { + NDPI_LOG_ERR(ndpi_str, "Empty TLS cert hash\n"); + return(-1); + } + + new_rule = (ndpi_tls_cert_hash_match_dynamic *)ndpi_malloc(sizeof(*new_rule)); + if(new_rule == NULL) { + NDPI_LOG_ERR(ndpi_str, "Memory allocation failure for TLS cert hash\n"); + return(-2); + } + + new_rule->cert_hash = ndpi_strdup(cert_hash); + if(new_rule->cert_hash == NULL) { + ndpi_free(new_rule); + NDPI_LOG_ERR(ndpi_str, "Memory allocation failure for TLS cert hash\n"); + return(-2); + } + new_rule->protocol_id = protocol_id; + new_rule->next = ndpi_str->dynamic_tls_cert_hash_list; + ndpi_str->dynamic_tls_cert_hash_list = new_rule; + + return(0); +} /* ******************************************* */ static int ndpi_add_http_url_subprotocol(struct ndpi_detection_module_struct *ndpi_str, @@ -4289,6 +4316,7 @@ struct ndpi_detection_module_struct *ndpi_init_detection_module(struct ndpi_glob ndpi_str->malicious_sha1_hashmap = NULL; /* Initialized on demand */ ndpi_str->ja4_custom_protos = NULL; /* Initialized on demand */ ndpi_str->ndpifp_custom_protos = NULL; /* Initialized on demand */ + ndpi_str->dynamic_tls_cert_hash_list = NULL; /* Initialized on demand */ ndpi_str->http_url_hashmap = NULL; /* Initialized on demand */ ndpi_str->trusted_issuer_dn = NULL; /* Initialized on demand */ @@ -5340,6 +5368,17 @@ void ndpi_exit_detection_module(struct ndpi_detection_module_struct *ndpi_str) { if(ndpi_str->ndpifp_custom_protos) ndpi_hash_free(&ndpi_str->ndpifp_custom_protos); + if(ndpi_str->dynamic_tls_cert_hash_list) { + ndpi_tls_cert_hash_match_dynamic *rule = ndpi_str->dynamic_tls_cert_hash_list; + + while(rule != NULL) { + ndpi_tls_cert_hash_match_dynamic *next = rule->next; + if(rule->cert_hash) + ndpi_free(rule->cert_hash); + ndpi_free(rule); + rule = next; + } + } if(ndpi_str->http_url_hashmap) ndpi_hash_free(&ndpi_str->http_url_hashmap); @@ -5647,6 +5686,61 @@ static int ndpi_handle_rule(struct ndpi_detection_module_struct *ndpi_str, ndpi_protocol_category_t category = NDPI_PROTOCOL_CATEGORY_UNSPECIFIED; ndpi_protocol_breed_t breed = NDPI_PROTOCOL_ACCEPTABLE; + if(strncmp(rule, "tls_cert_hash:", 14) == 0) { + + char *hash_start = &rule[14]; + char *at_sign = strchr(hash_start, '@'); + + if(at_sign != NULL) { + int hash_len = at_sign - hash_start; + char *cert_hash = (char *)ndpi_malloc(hash_len + 1); + if(cert_hash == NULL) { + NDPI_LOG_ERR(ndpi_str, "Memory allocation failure for TLS cert hash\n"); + return(-1); + } + strncpy(cert_hash, hash_start, hash_len); + cert_hash[hash_len] = '\0'; + char *proto_name = &at_sign[1]; + u_int16_t proto_id = ndpi_get_proto_by_name(ndpi_str, proto_name); + + if(proto_id == NDPI_PROTOCOL_UNKNOWN) { + if(ndpi_str->num_supported_protocols >= 65535) { + NDPI_LOG_ERR(ndpi_str, "Too many protocols defined\n"); + ndpi_free(cert_hash); + return(-2); + } + + proto_id = ndpi_str->num_supported_protocols; + ndpi_port_range ports_a[MAX_DEFAULT_PORTS], ports_b[MAX_DEFAULT_PORTS]; + + ret = ndpi_set_proto_defaults(ndpi_str, 1, 1, + NDPI_PROTOCOL_ACCEPTABLE, + proto_id, + proto_name, + NDPI_PROTOCOL_CATEGORY_UNSPECIFIED, + NDPI_PROTOCOL_QOE_CATEGORY_UNSPECIFIED, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0), + 1); + if(ret != 0) { + NDPI_LOG_ERR(ndpi_str, "Error creating protocol. Skip rule\n"); + ndpi_free(cert_hash); + return(-3); + } + } + + /*hash rule*/ + ret = ndpi_add_tls_cert_hash_subprotocol(ndpi_str, cert_hash, proto_id); + printf("[DEBUG] Added TLS cert hash rule: hash=%s, proto_id=%u, ret=%d\n", + cert_hash, proto_id, ret); + ndpi_free(cert_hash); + return(ret); + } else { + NDPI_LOG_ERR(ndpi_str, "Invalid TLS cert hash rule format: %s\n", rule); + return(-1); + } + } + at = strrchr(rule, '@'); if(at == NULL) { /* This looks like a mask rule or an invalid rule */ @@ -5810,7 +5904,7 @@ static int ndpi_handle_rule(struct ndpi_detection_module_struct *ndpi_str, while((elem = strsep(&rule, ",")) != NULL) { char *attr = elem, *value = NULL; ndpi_port_range range; - int is_tcp = 0, is_udp = 0, is_ip = 0, is_ja4 = 0, is_ndpifp = 0, is_httpurl = 0;; + int is_tcp = 0, is_udp = 0, is_ip = 0, is_ja4 = 0, is_ndpifp = 0, is_httpurl = 0; u_int8_t is_ipv6_ip = 0; if(strncmp(attr, "tcp:", 4) == 0) @@ -5924,7 +6018,7 @@ static int ndpi_handle_rule(struct ndpi_detection_module_struct *ndpi_str, if(rc != 0) return(rc); - } else { + }else { int rc = ndpi_add_host_url_subprotocol(ndpi_str, value, subprotocol_id, category, breed, 0); if(rc != 0) @@ -5934,6 +6028,7 @@ static int ndpi_handle_rule(struct ndpi_detection_module_struct *ndpi_str, return(ret); } + /* ******************************************************************** */ diff --git a/src/lib/protocols/tls.c b/src/lib/protocols/tls.c index 4e8b16806c1..315b3bd3f65 100644 --- a/src/lib/protocols/tls.c +++ b/src/lib/protocols/tls.c @@ -675,6 +675,8 @@ static void checkTLSSubprotocol(struct ndpi_detection_module_struct *ndpi_struct void processCertificateElements(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow, u_int16_t p_offset, u_int16_t certificate_len) { + printf("[DEBUG] processCertificateElements called! cert_len=%u\n", certificate_len); + fflush(stdout); struct ndpi_packet_struct *packet = &ndpi_struct->packet; u_int16_t num_found = 0; int32_t i; @@ -1094,6 +1096,8 @@ void processCertificateElements(struct ndpi_detection_module_struct *ndpi_struct } } } + + } if(flow->protos.tls_quic.subjectDN && flow->protos.tls_quic.issuerDN @@ -1218,6 +1222,7 @@ int processCertificate(struct ndpi_detection_module_struct *ndpi_struct, printf("\n"); } #endif +printf("[DEBUG] tls_sha1_fingerprint_enabled=%d\n", ndpi_struct->cfg.tls_sha1_fingerprint_enabled); /* For SHA-1 we take into account only the first certificate and not all of them */ if(ndpi_struct->cfg.tls_sha1_fingerprint_enabled) { @@ -1255,10 +1260,57 @@ int processCertificate(struct ndpi_detection_module_struct *ndpi_struct, if(rc1 == 0) ndpi_set_risk(ndpi_struct, flow, NDPI_MALICIOUS_SHA1_CERTIFICATE, sha1_str); } + printf("[DEBUG_CONDITIONS] detected_protocol_stack[1]=%u (should be 0), list_exists=%d\n", + flow->detected_protocol_stack[1], ndpi_struct->dynamic_tls_cert_hash_list != NULL); + + if(flow->detected_protocol_stack[1] == NDPI_PROTOCOL_UNKNOWN && + ndpi_struct->dynamic_tls_cert_hash_list != NULL) { + printf("[DEBUG] ✅ Both conditions met! Checking TLS cert hashes\n"); + + ndpi_tls_cert_hash_match_dynamic *rule = ndpi_struct->dynamic_tls_cert_hash_list; + + while(rule != NULL) { + char rule_hash_no_colon[256]; + int j = 0; + for(int i = 0; rule->cert_hash[i] != '\0' && j < (int)sizeof(rule_hash_no_colon)-1; i++) { + if(rule->cert_hash[i] != ':') { + rule_hash_no_colon[j++] = toupper((unsigned char)rule->cert_hash[i]); + } + } + rule_hash_no_colon[j] = '\0'; + + + if(strcasecmp(sha1_str, rule_hash_no_colon) == 0) { + printf("[DEBUG] ✅ HASH MATCH! sha1_str=%s\n", sha1_str); + + /* Hash match found */ + ndpi_master_app_protocol proto; + + ndpi_set_detected_protocol(ndpi_struct, flow, rule->protocol_id, + ndpi_get_master_proto(ndpi_struct, flow), + NDPI_CONFIDENCE_DPI); + proto.master_protocol = ndpi_get_master_proto(ndpi_struct, flow); + proto.app_protocol = rule->protocol_id; + flow->category = get_proto_category(ndpi_struct, proto); + flow->breed = get_proto_breed(ndpi_struct, proto); + ndpi_check_subprotocol_risk(ndpi_struct, flow, rule->protocol_id); + ndpi_unset_risk(ndpi_struct, flow, NDPI_NUMERIC_IP_HOST); + break; + }else { + printf("[DEBUG] ❌ No match. sha1_str=%s vs rule=%s\n", sha1_str, rule_hash_no_colon); + } + rule = rule->next; + } + } + else { + printf("[DEBUG] ❌ Conditions NOT met: stack[1]=%u (need 0), list=%d (need 1)\n", + flow->detected_protocol_stack[1], + ndpi_struct->dynamic_tls_cert_hash_list != NULL); } + } processCertificateElements(ndpi_struct, flow, certificates_offset, certificate_len); - } + } certificates_offset += certificate_len; } diff --git a/test_protos_hash.txt b/test_protos_hash.txt new file mode 100644 index 00000000000..b8080793b7d --- /dev/null +++ b/test_protos_hash.txt @@ -0,0 +1 @@ +nDPI_TestApp@tls_cert_hash:"da:39:a3:ee:5e:6b:4b:0d:32:55:bf:ef:95:60:18:90:af:d8:07:09" diff --git a/tls_test_cert/test_cert.pem b/tls_test_cert/test_cert.pem new file mode 100644 index 00000000000..2e6f9f3dcc5 --- /dev/null +++ b/tls_test_cert/test_cert.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEAzCCAuugAwIBAgIUGpLRiLOOApm+pLR7eUZBQmXwd/AwDQYJKoZIhvcNAQEL +BQAwgZAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH +DA1TYW4gRnJhbmNpc2NvMR8wHQYDVQQKDBZuRFBJX1Rlc3RfT3JnYW5pemF0aW9u +MRAwDgYDVQQLDAdUZXN0aW5nMSEwHwYDVQQDDBhuRFBJX1Rlc3RBcHBfQ2VydGlm +aWNhdGUwHhcNMjYwNzI1MTAwNjQ2WhcNMjcwNzI1MTAwNjQ2WjCBkDELMAkGA1UE +BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lz +Y28xHzAdBgNVBAoMFm5EUElfVGVzdF9Pcmdhbml6YXRpb24xEDAOBgNVBAsMB1Rl +c3RpbmcxITAfBgNVBAMMGG5EUElfVGVzdEFwcF9DZXJ0aWZpY2F0ZTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBANbsezILR6MkDuSWPRKlNhGfy0ItXbw7 +9S1F9NFevvckTwvyK7CtTNh4SeukgFqTnwIe5t0WMKg8I6DVUjSAP+BctMk7ZC0q +yfTSijMQ4DPjAFmj9DztZpMTwP/YfKaNIO4qTDknZ7LaOBVaIjdsL2++W9wFo6hq +zMAhfjcfvLp70oWGPz7i3dhvJ925ymbrrDXY2mpNmEhuAB5MtBIL6pxP389ygWyo +8QrnTzhErt3JDSq+N73bNeq1t/hwV7Ufg+SBNtURoT6vLpOgwbAi3w3zk3mgPoxO +iB6YoNhpi4Sqh0Dzbvf2UFVGClfzKsB40NsmGrjFs7XgFIoqIAv8H2sCAwEAAaNT +MFEwHQYDVR0OBBYEFOqbaHUFL/kbmDjQie14VJ7Uvf2hMB8GA1UdIwQYMBaAFOqb +aHUFL/kbmDjQie14VJ7Uvf2hMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAEctiMs+zoaQ7LCk00geSCJFnFoeNYL/LFetksJRiCJkDC1Hy9PVtn86 +axYNai2I+w1eEOdNjHR9E43Cl3bxwnQM064iekEdhGQmjfDYVXjdQtTt8Y0Gs/95 +O/cnDVotmcfQdRzkNAlc5KEviy0YepKMajmBYRFwbg9Up6UGKjeHreZA0ebUx/BE +plCkBANCSPnBGLnTJD8YOkPORqlutAJtxDB5IWrjrlfjnX5h++mmS5B263C/TR4m +3BRnXMEhft056NDPDMzZI+fxGAn4xpMejd4ZfZd6l+YeYpxZyMn/4WuuyglnEDhp +ptU731ISG3+QJKG8+LHIlz78xx3GDxw= +-----END CERTIFICATE----- diff --git a/tls_test_cert/test_key.pem b/tls_test_cert/test_key.pem new file mode 100644 index 00000000000..720020a7a06 --- /dev/null +++ b/tls_test_cert/test_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDW7HsyC0ejJA7k +lj0SpTYRn8tCLV28O/UtRfTRXr73JE8L8iuwrUzYeEnrpIBak58CHubdFjCoPCOg +1VI0gD/gXLTJO2QtKsn00oozEOAz4wBZo/Q87WaTE8D/2HymjSDuKkw5J2ey2jgV +WiI3bC9vvlvcBaOoaszAIX43H7y6e9KFhj8+4t3Ybyfducpm66w12NpqTZhIbgAe +TLQSC+qcT9/PcoFsqPEK5084RK7dyQ0qvje92zXqtbf4cFe1H4PkgTbVEaE+ry6T +oMGwIt8N85N5oD6MTogemKDYaYuEqodA82739lBVRgpX8yrAeNDbJhq4xbO14BSK +KiAL/B9rAgMBAAECggEAAnHfNuME9hifaL9Cw5cIhevM6pxMgCsob1Ln5YfP4UzS +715aW+CkyqOXp6wP37flK6eBVct+UzOX7ijvDfTVhS5Yyh84VQEzGetZMhqDimOP +KfXg/d5vd8XWQLLMPDLHyJ3HjzDhD5pG4AtN71Z2Na2zKs4PngdU++yFnIfqZC6m +EMCnaVy+vG/FH072IOD7g9avBv3y765crOZnskscbzjsh8GGqbNYAM8WzoHQLaxg +/JXNtMcg5E92vLhoIdk5oicsxGNbppl+SqBxp3M0R0qjpYNybaeZ9aBdFgn9lxii +aZSZJoL7o1eKtabg3hipZaUnZtxMN7X5Kw2jyDw0PQKBgQDrQfz2J/01Ji/EGn/M +npAXgFUO+MWetWKVGdScj2d0GO7DHekoN4+CkQUthyY8y/30nXIRGnY+Y4JugpOi +JP7htK4oDOzGYS6MMT+kUMv1FnWhmbFH1JjaGElVOXy3lct+QYYRCX6tZXEsXYdK +74dLtYwcxOjcOuCdWltRPOPjXQKBgQDp34iK2ACU44XD4so9BZ2Mh26M4rABQY7Q +vmOgdM0TrEq0aWeNU1DR1g2H/Xuc/5LlZg0Jw+biFkX8TiaW6c1d5K/jJjDcQN2P +SOMqHUjtkufnLVYsbH8KfbMmZWFdmqmia8OmsXgZo7VB1tmU9wzSeg8SuRLmPlLH +tBSSwgzpZwKBgGXygpxpV+DgW3KvyRHy5J5KjpGeXIUaNU5Hk0sFGd/FrjH/lDpJ +WXdLQOMp3fgarkKCuBuRTAxdMviQvUlWnt1QXugcMb8F/hXigaHDIZ9jzDXrVFe8 +WUCppZ24+8LStwxPeLmJU8vwWcrP/QEMK6UzVzRgYEiPeya1MT1TFa5BAoGBAJvR +Ypee8kRAko7AOx4M77sBQJZ9MeakVxKcxqPRLhE9aUhqMaPnLqGfi8RfDky7upM8 +OtOWwe1ACcDgELXbcAhupQEiIMueK/+bAD6+5CflCLztZ8yRPNMwjHu4b/Z9ZSfh +xKj69JmgiNMk4jJtpw1UZVaEGCNuwphrUom04AVhAoGAWINTXulnmcjN9qNlgeQK +gx6tOjkLXms0E+Qzc5DoQQs10tqV7OCoqCyaAB1jG5qTSH8S90pymZyrYVWbwYZu +UeejMmWyz2LHbXsLNJhNNyhHXryjngDTMHQZdPAe4D0JVpaQneR89zC0fgjjxJqs +F4XdupKVcAoySZ97HvAjbaw= +-----END PRIVATE KEY----- diff --git a/tls_test_cert/test_protos.txt b/tls_test_cert/test_protos.txt new file mode 100644 index 00000000000..d70f716459b --- /dev/null +++ b/tls_test_cert/test_protos.txt @@ -0,0 +1,2 @@ +# Match by TLS certificate hash +tls_cert_hash:0a:a0:ed:1a:71:50:5d:37:c4:4f:8f:d0:9a:0f:3e:a0:17:6e:e9:58@nDPI_TestApp diff --git a/tls_test_cert/tls_cert_test.pcap b/tls_test_cert/tls_cert_test.pcap new file mode 100644 index 00000000000..9cebdebc764 Binary files /dev/null and b/tls_test_cert/tls_cert_test.pcap differ