From 37ca0f016e0d0696642a13fe09b01989f1429cd9 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Wed, 13 May 2026 21:38:32 +0200 Subject: [PATCH 1/8] outbount tls connections versions detection --- modules/test/tls/python/src/tls_util.py | 37 +++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/modules/test/tls/python/src/tls_util.py b/modules/test/tls/python/src/tls_util.py index 7859b6629..1bf5051dc 100644 --- a/modules/test/tls/python/src/tls_util.py +++ b/modules/test/tls/python/src/tls_util.py @@ -955,6 +955,43 @@ def validate_tls_client(self, connection detected to {ip}\n''' return tls_client_valid, tls_client_details + def detect_tls_client_versions(self, + client_mac: str, + capture_files: list[str], + version_list: list[str] | None = None + ) -> dict: + """Detect all TLS client versions from packet captures.""" + if version_list is None: + version_list = ['1.0', '1.1', '1.2', '1.3'] + + version_results = {} + for tls_version in version_list: + tls_packets = self.get_tls_packets(capture_files, client_mac, + tls_version) + tls_present = False + details = [] + unique_packets = self._get_unique_packets(tls_packets) + for packet in unique_packets: + dst_ip = packet['dst_ip'] + details.append(f'TLS {tls_version} packet detected to IP: {dst_ip}') + if tls_packets: + tls_present = True + version_results[tls_version] = { + 'present': tls_present, + 'details': details + } + return version_results + + def _get_unique_packets(self, packets: list[dict]) -> list[dict]: + unique_packets = [] + seen_dst_ips = set() + for packet in packets: + dst_ip = packet['dst_ip'] + if dst_ip not in seen_dst_ips: + unique_packets.append(packet) + seen_dst_ips.add(dst_ip) + return unique_packets + def is_ecdh_and_ecdsa(self, ciphers): ecdh = False ecdsa = False From c3916f4ae7983fab87d2c3c484a284de1aa15fde Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Thu, 14 May 2026 11:14:42 +0200 Subject: [PATCH 2/8] remove TLS 1.0 from compliant virtual device --- test_vm/provision-compliant.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/test_vm/provision-compliant.yml b/test_vm/provision-compliant.yml index 0ebd59765..aba4d78e0 100644 --- a/test_vm/provision-compliant.yml +++ b/test_vm/provision-compliant.yml @@ -270,8 +270,6 @@ fi sleep 1 done - # TLS 1.0 - openssl s_client -connect tls-v1-0.badssl.com:1010 -tls1 -bind "$IP" # TLS 1.2 openssl s_client -connect tls-v1-2.badssl.com:1012 -tls1_2 -bind "$IP" # TLS 1.3 From e49cda6d467ab12ad30bb84deaa75414a85622c9 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Thu, 14 May 2026 19:55:17 +0200 Subject: [PATCH 3/8] compliant vm --- test_vm/provision-compliant.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test_vm/provision-compliant.yml b/test_vm/provision-compliant.yml index aba4d78e0..92c6b9a6c 100644 --- a/test_vm/provision-compliant.yml +++ b/test_vm/provision-compliant.yml @@ -270,11 +270,10 @@ fi sleep 1 done - # TLS 1.2 - openssl s_client -connect tls-v1-2.badssl.com:1012 -tls1_2 -bind "$IP" - # TLS 1.3 - openssl s_client -connect tls13.badssl.com:443 -tls1_3 -bind "$IP" - + # TLS 1.2 (using OpenSSL 3.1.4) + timeout 3 openssl s_client -connect tls-v1-2.badssl.com:1012 -tls1_2 -bind "$IP" -quiet /dev/null || true + # TLS 1.3 (using OpenSSL 3.1.4) + timeout 3 openssl s_client -connect google.com:443 -tls1_3 -bind "$IP" -quiet /dev/null || true - name: Create systemd service for TLS emulation ansible.builtin.copy: dest: /etc/systemd/system/tls-emulate.service From dc2136cc9075934830598ace5e1d7a7de8398b36 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Thu, 14 May 2026 19:55:33 +0200 Subject: [PATCH 4/8] improve get hello packets script --- modules/test/tls/bin/get_client_hello_packets.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/test/tls/bin/get_client_hello_packets.sh b/modules/test/tls/bin/get_client_hello_packets.sh index fd06a3d28..d6075d48f 100755 --- a/modules/test/tls/bin/get_client_hello_packets.sh +++ b/modules/test/tls/bin/get_client_hello_packets.sh @@ -26,7 +26,7 @@ if [[ $TLS_VERSION == '1.0' ]]; then elif [[ $TLS_VERSION == '1.1' ]]; then TSHARK_FILTER="$TSHARK_FILTER and ssl.handshake.version==0x0302" elif [[ $TLS_VERSION == '1.2' || -z $TLS_VERSION ]]; then - TSHARK_FILTER="$TSHARK_FILTER and ssl.handshake.version==0x0303" + TSHARK_FILTER="$TSHARK_FILTER and ssl.handshake.version==0x0303 and !tls.handshake.extensions.supported_version==0x0304" elif [[ $TLS_VERSION == '1.3' ]]; then TSHARK_FILTER="$TSHARK_FILTER and (ssl.handshake.version==0x0304 or tls.handshake.extensions.supported_version==0x0304)" else From 804c05bd22647b9e3317ec69138a63de348477cd Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Thu, 14 May 2026 20:13:49 +0200 Subject: [PATCH 5/8] invalid vm device --- test_vm/provision-non-compliant-tls.yml | 75 +++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 4 deletions(-) diff --git a/test_vm/provision-non-compliant-tls.yml b/test_vm/provision-non-compliant-tls.yml index 2c6685925..4dc419c59 100644 --- a/test_vm/provision-non-compliant-tls.yml +++ b/test_vm/provision-non-compliant-tls.yml @@ -12,20 +12,73 @@ ssl_key: /etc/ssl/private/nginx-selfsigned.key tasks: - - name: Ensure openssl, dhclient, and nginx are installed + - name: Ensure openssl, dhclient, nginx and build dependencies are installed apt: name: - openssl - isc-dhcp-client - nginx + - perl + - pkg-config + - zlib1g-dev + - wget + - gcc + - make state: present update_cache: yes + - name: Download OpenSSL 1.1.1u source + get_url: + url: https://www.openssl.org/source/openssl-1.1.1u.tar.gz + dest: /tmp/openssl-1.1.1u.tar.gz + mode: '0644' + force: no + + - name: Extract OpenSSL 1.1.1u source + unarchive: + src: /tmp/openssl-1.1.1u.tar.gz + dest: /tmp + remote_src: yes + creates: /tmp/openssl-1.1.1u + + - name: Build and install OpenSSL 1.1.1 + shell: | + ./config --prefix=/opt/openssl-1.1.1 no-shared + make -j"$(nproc)" + make install_sw + args: + chdir: /tmp/openssl-1.1.1u + creates: /opt/openssl-1.1.1/bin/openssl + - name: Run dhclient on ens5 (in background) shell: nohup dhclient {{ iface }} & async: 0 poll: 0 + - name: Download OpenSSL 3.1.4 source + get_url: + url: https://www.openssl.org/source/openssl-3.1.4.tar.gz + dest: /tmp/openssl-3.1.4.tar.gz + mode: '0644' + force: no + + - name: Extract OpenSSL 3.1.4 source + unarchive: + src: /tmp/openssl-3.1.4.tar.gz + dest: /tmp + remote_src: yes + creates: /tmp/openssl-3.1.4 + + - name: Build and install OpenSSL 3.1.4 + shell: | + ./config --prefix=/opt/openssl-3.1.4 no-shared + make -j"$(nproc)" + make install_sw + args: + chdir: /tmp/openssl-3.1.4 + creates: /opt/openssl-3.1.4/bin/openssl + + # NGINX - name: Generate self-signed SSL certificate for nginx @@ -77,6 +130,14 @@ state: restarted enabled: yes + - name: Create custom OpenSSL config for legacy protocol support + copy: + dest: /etc/ssl/openssl-legacy.cnf + content: | + .include /etc/ssl/openssl.cnf + [system_default_sect] + MinProtocol = TLSv1.0 + MaxProtocol = TLSv1.3 - name: Copy fail_tls_handshake.sh script copy: @@ -105,11 +166,17 @@ echo "No IP $ip_addr on $iface" >> $LOG exit 1 fi - # TLS 1.2 + export OPENSSL_CONF=/etc/ssl/openssl-legacy.cnf + OLD_OPENSSL=/opt/openssl-1.1.1/bin/openssl + NEW_OPENSSL=/opt/openssl-3.1.4/bin/openssl + echo "Running TLS 1.0 handshake..." >> $LOG + timeout 3 "$OLD_OPENSSL" s_client -connect tls-v1-0.badssl.com:1010 -tls1 -bind $ip_addr -quiet < /dev/null >> $LOG 2>&1 || true + echo "Running TLS 1.1 handshake..." >> $LOG + timeout 3 "$OLD_OPENSSL" s_client -connect tls-v1-1.badssl.com:1011 -tls1_1 -bind $ip_addr -quiet < /dev/null >> $LOG 2>&1 || true echo "Running TLS 1.2 handshake..." >> $LOG - openssl s_client -connect $host:$port -tls1_2 -cipher "CAMELLIA256-SHA256" -bind $ip_addr -ign_eof < /dev/null >> $LOG 2>&1 + "$NEW_OPENSSL" s_client -connect $host:$port -tls1_2 -cipher "CAMELLIA256-SHA256" -bind $ip_addr -ign_eof < /dev/null >> $LOG 2>&1 echo "Running TLS 1.3 handshake..." >> $LOG - openssl s_client -connect tls-v1-2.badssl.com:1012 -tls1_3 + "$NEW_OPENSSL" s_client -connect tls-v1-2.badssl.com:1012 -tls1_3 - name: Create systemd service for failing TLS handshake From b26184bc9257ec77576748d893e16128ad5a40b6 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Thu, 14 May 2026 20:15:33 +0200 Subject: [PATCH 6/8] tls 1.0 new implementation --- modules/test/tls/python/src/tls_module.py | 63 +++++++++++------------ 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/modules/test/tls/python/src/tls_module.py b/modules/test/tls/python/src/tls_module.py index bec708d3f..e0cf9f0d3 100644 --- a/modules/test/tls/python/src/tls_module.py +++ b/modules/test/tls/python/src/tls_module.py @@ -507,39 +507,38 @@ def _security_tls_v1_3_server(self): def _security_tls_v1_0_client(self): LOGGER.info('Running security.tls.v1_0_client') - tls_1_0_valid = self._validate_tls_client(self._device_mac, '1.0') - tls_1_1_valid = self._validate_tls_client(self._device_mac, '1.1') - tls_1_2_valid = self._validate_tls_client(self._device_mac, '1.2') - tls_1_3_valid = self._validate_tls_client(self._device_mac, '1.3') - states = [ - tls_1_0_valid[0], tls_1_1_valid[0], tls_1_2_valid[0], tls_1_3_valid[0] - ] - if any(state is True for state in states): - # If any state is True, return True - result_state = True - result_message = 'TLS 1.0 or higher detected' - elif all(state == 'Feature Not Detected' for state in states): - # If all states are "Feature not Detected" - result_state = 'Feature Not Detected' - result_message = tls_1_0_valid[1] - elif all(state == 'Error' for state in states): - # If all states are "Error" - result_state = 'Error' - result_message = '' - else: + try: + tls_versions = self._tls_util.detect_tls_client_versions( + client_mac=self._device_mac, + capture_files=[ + MONITOR_CAPTURE_FILE, STARTUP_CAPTURE_FILE, TLS_CAPTURE_FILE + ] + ) + LOGGER.info(f'TLS client version detection results: {tls_versions}') + except Exception as e: + LOGGER.error(f'Error detecting TLS client versions: {e}') + return 'Error', f'Error detecting TLS client versions: {e}', [] + result_state = 'Feature Not Detected' + result_message = 'No TLS client connections detected.' + result_details = [] + + if tls_versions['1.0']['present'] or tls_versions['1.1']['present']: result_state = False - result_message = 'TLS 1.0 or higher was not detected' - result_details = tuple({ - *tls_1_0_valid[2], - *tls_1_1_valid[2], - *tls_1_2_valid[2], - *tls_1_3_valid[2] - }) - LOGGER.info(f'TLS 1.0 Client Validation details: {result_details}') - result_tags = list( - set(tls_1_0_valid[3] + tls_1_1_valid[3] + tls_1_2_valid[3] + - tls_1_3_valid[3])) - return result_state, result_message, result_details, result_tags + result_message = 'TLS 1.0 or TLS 1.1 detected.' + LOGGER.info(result_message) + result_details.extend(tls_versions['1.0']['details']) + result_details.extend(tls_versions['1.1']['details']) + for detail in result_details: + LOGGER.info(detail) + elif tls_versions['1.2']['present'] or tls_versions['1.3']['present']: + result_state = True + result_message = 'TLS 1.2 or higher detected.' + LOGGER.info(result_message) + result_details.extend(tls_versions['1.2']['details']) + result_details.extend(tls_versions['1.3']['details']) + for detail in result_details: + LOGGER.info(detail) + return result_state, result_message, result_details def _security_tls_v1_2_client(self): LOGGER.info('Running security.tls.v1_2_client') From d92f24a4decd5425f73069bcef4d5a40c206b616 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Mon, 18 May 2026 17:49:20 +0200 Subject: [PATCH 7/8] TLS 1.0 documentation --- modules/test/tls/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/test/tls/README.md b/modules/test/tls/README.md index c1f0c9072..cfca8100c 100644 --- a/modules/test/tls/README.md +++ b/modules/test/tls/README.md @@ -14,7 +14,7 @@ Within the ```python/src``` directory, the below tests are executed. | ID | Description | Expected behavior | Required result |---|---|---|---| -| security.tls.v1_0_client | Device uses TLS with connection to an external service on port 443 (or any other port which could be running the webserver-HTTPS) | The packet indicates a TLS connection with at least TLS 1.0 and support | Informational | +| security.tls.v1_0_client | Device uses TLS with connection to an external service on port 443 (or any other port which could be running the webserver-HTTPS) | The packet indicates a TLS connection with at least TLS 1.2 and support | Required if Applicable | | security.tls.v1_2_server | Check the device web server TLS 1.2 and the certificate is valid | TLS 1.2 certificate is issues to the web browser client when accessed | Required if Applicable | | security.tls.v1_2_client | Device uses TLS with connection to an external service on port 443 (or any other port which could be running the webserver-HTTPS) | The packet indicates a TLS connection with at least TLS v1.2 and support for ECDH and ECDSA ciphers | Required if Applicable | | security.tls.v1_3_server | Check the device web server TLS 1.3 and the certificate is valid | TLS 1.3 certificate is issued to the web browser client when accessed | Informational | From 5fe78d9bf47729552d3da0244f121adf403a6eee Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Mon, 18 May 2026 19:23:49 +0200 Subject: [PATCH 8/8] unit tests --- testing/unit/tls/tls_module_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/unit/tls/tls_module_test.py b/testing/unit/tls/tls_module_test.py index 26aa43d03..a0364b4ce 100644 --- a/testing/unit/tls/tls_module_test.py +++ b/testing/unit/tls/tls_module_test.py @@ -607,9 +607,9 @@ def security_tls_client_allowed_protocols_test(self): # Run the client test test_results = TLS_UTIL.validate_tls_client(client_mac='e4:5f:01:5f:92:9c', - tls_version='1.2', + tls_version='1.3', capture_files=[capture_file]) - print(str(test_results)) + print('results', str(test_results)) self.assertTrue(test_results[0]) def outbound_connections_test(self):