From c2a23449d0521a9e95429a596290e99ebbdb3ecf Mon Sep 17 00:00:00 2001 From: "Benjamin W. Broersma" Date: Fri, 19 Jun 2026 19:38:03 +0200 Subject: [PATCH 1/3] Fix #2031 - TLS sigalgs / key exchange: optimize happy flow connections --- checks/tasks/tls/scans.py | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/checks/tasks/tls/scans.py b/checks/tasks/tls/scans.py index c553fd2fa..a652b95c3 100644 --- a/checks/tasks/tls/scans.py +++ b/checks/tasks/tls/scans.py @@ -868,24 +868,33 @@ def test_key_exchange_hash( Note that this is not the certificate hash, or TLS cipher hash. There are few or no hosts that do not meet this requirement. """ - bad_hash_result = _test_connection_with_limited_sigalgs(server_connectivity_info, SIGNATURE_ALGORITHMS_BAD_HASH) - if bad_hash_result: - log.info(f"SHA2 key exchange check: negotiated bad sigalg ({bad_hash_result})") - return KeyExchangeHashFunctionEvaluation( - status=KexHashFuncStatus.bad, - score=scoring.WEB_TLS_KEX_HASH_FUNC_BAD, - found_hash=bad_hash_result.name, - ) - - phase_out_hash_result = _test_connection_with_limited_sigalgs( - server_connectivity_info, SIGNATURE_ALGORITHMS_PHASE_OUT_HASH + bad_phase_out_hash_result = _test_connection_with_limited_sigalgs( + server_connectivity_info, SIGNATURE_ALGORITHMS_BAD_HASH + SIGNATURE_ALGORITHMS_PHASE_OUT_HASH ) - if phase_out_hash_result: - log.info(f"SHA2 key exchange check: negotiated phase_out hash ({phase_out_hash_result})") + if bad_phase_out_hash_result: + bad_hash_result = None + log.info(f"key exchange check: negotiated bad or phase_out sigalg ({bad_phase_out_hash_result.name})") + if bad_phase_out_hash_result in [sa[0] for sa in SIGNATURE_ALGORITHMS_PHASE_OUT_HASH]: + log.info("key exchange check: since it was phase_out, recheck with only bad") + bad_hash_result = _test_connection_with_limited_sigalgs( + server_connectivity_info, SIGNATURE_ALGORITHMS_BAD_HASH + ) + + if (bad_phase_out_hash_result in [sa[0] for sa in SIGNATURE_ALGORITHMS_BAD_HASH]) or bad_hash_result: + log.info( + f"key exchange check: negotiated bad sigalg ({(bad_hash_result or bad_phase_out_hash_result).name})" + ) + return KeyExchangeHashFunctionEvaluation( + status=KexHashFuncStatus.bad, + score=scoring.WEB_TLS_KEX_HASH_FUNC_BAD, + found_hash=bad_hash_result.name, + ) + + log.info(f"key exchange check: negotiated phase_out sigalg ({bad_phase_out_hash_result.name})") return KeyExchangeHashFunctionEvaluation( status=KexHashFuncStatus.phase_out, score=scoring.WEB_TLS_KEX_HASH_FUNC_OK, - found_hash=phase_out_hash_result.name, + found_hash=bad_phase_out_hash_result.name, ) return KeyExchangeHashFunctionEvaluation( From c2a0e6c76c12fe226a9d11871be472b46c4086a5 Mon Sep 17 00:00:00 2001 From: Sasha Romijn Date: Fri, 3 Jul 2026 15:23:21 +0200 Subject: [PATCH 2/3] refactor --- checks/tasks/tls/scans.py | 54 +++++++++++++++++-------------- checks/tasks/tls/tls_constants.py | 1 + 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/checks/tasks/tls/scans.py b/checks/tasks/tls/scans.py index a652b95c3..972ae91e9 100644 --- a/checks/tasks/tls/scans.py +++ b/checks/tasks/tls/scans.py @@ -89,6 +89,7 @@ CERT_RSA_MIN_GOOD_KEY_SIZE, CERT_RSA_MIN_PHASE_OUT_KEY_SIZE, SIGNATURE_ALGORITHMS_BAD_HASH, + SIGNATURE_ALGORITHMS_BAD_HASH_NIDS, SIGNATURE_ALGORITHMS_PHASE_OUT_HASH, TLS_1_3_PROBE_CIPHERS, ) @@ -866,40 +867,43 @@ def test_key_exchange_hash( """ Test key exchange hashes per NCSC 3.3.5. Note that this is not the certificate hash, or TLS cipher hash. - There are few or no hosts that do not meet this requirement. + The initial test is phase_out+bad to keep happy path connections low. """ - bad_phase_out_hash_result = _test_connection_with_limited_sigalgs( + probe_bad_phase_out = _test_connection_with_limited_sigalgs( server_connectivity_info, SIGNATURE_ALGORITHMS_BAD_HASH + SIGNATURE_ALGORITHMS_PHASE_OUT_HASH ) - if bad_phase_out_hash_result: - bad_hash_result = None - log.info(f"key exchange check: negotiated bad or phase_out sigalg ({bad_phase_out_hash_result.name})") - if bad_phase_out_hash_result in [sa[0] for sa in SIGNATURE_ALGORITHMS_PHASE_OUT_HASH]: - log.info("key exchange check: since it was phase_out, recheck with only bad") - bad_hash_result = _test_connection_with_limited_sigalgs( - server_connectivity_info, SIGNATURE_ALGORITHMS_BAD_HASH - ) + if not probe_bad_phase_out: + return KeyExchangeHashFunctionEvaluation( + status=KexHashFuncStatus.good, + score=scoring.WEB_TLS_KEX_HASH_FUNC_GOOD, + ) - if (bad_phase_out_hash_result in [sa[0] for sa in SIGNATURE_ALGORITHMS_BAD_HASH]) or bad_hash_result: - log.info( - f"key exchange check: negotiated bad sigalg ({(bad_hash_result or bad_phase_out_hash_result).name})" - ) - return KeyExchangeHashFunctionEvaluation( - status=KexHashFuncStatus.bad, - score=scoring.WEB_TLS_KEX_HASH_FUNC_BAD, - found_hash=bad_hash_result.name, - ) + if probe_bad_phase_out in SIGNATURE_ALGORITHMS_BAD_HASH_NIDS: + log.info(f"key exchange check: negotiated bad sigalg ({probe_bad_phase_out.name})") + return KeyExchangeHashFunctionEvaluation( + status=KexHashFuncStatus.bad, + score=scoring.WEB_TLS_KEX_HASH_FUNC_BAD, + found_hash=probe_bad_phase_out.name, + ) - log.info(f"key exchange check: negotiated phase_out sigalg ({bad_phase_out_hash_result.name})") + log.info( + f"key exchange check: initial probe negotiated phase_out sigalg ({probe_bad_phase_out.name}), " + "testing with only bad sigalgs" + ) + probe_bad = _test_connection_with_limited_sigalgs(server_connectivity_info, SIGNATURE_ALGORITHMS_BAD_HASH) + if probe_bad: + log.info(f"key exchange check: negotiated bad sigalg ({probe_bad.name})") return KeyExchangeHashFunctionEvaluation( - status=KexHashFuncStatus.phase_out, - score=scoring.WEB_TLS_KEX_HASH_FUNC_OK, - found_hash=bad_phase_out_hash_result.name, + status=KexHashFuncStatus.bad, + score=scoring.WEB_TLS_KEX_HASH_FUNC_BAD, + found_hash=probe_bad.name, ) + log.info(f"key exchange check: negotiated phase_out sigalg ({probe_bad_phase_out.name})") return KeyExchangeHashFunctionEvaluation( - status=KexHashFuncStatus.good, - score=scoring.WEB_TLS_KEX_HASH_FUNC_GOOD, + status=KexHashFuncStatus.phase_out, + score=scoring.WEB_TLS_KEX_HASH_FUNC_OK, + found_hash=probe_bad_phase_out.name, ) diff --git a/checks/tasks/tls/tls_constants.py b/checks/tasks/tls/tls_constants.py index 7bacb5432..c484d6aad 100644 --- a/checks/tasks/tls/tls_constants.py +++ b/checks/tasks/tls/tls_constants.py @@ -141,6 +141,7 @@ (OpenSslDigestNidEnum.SHA224, OpenSslEvpPkeyEnum.RSA), (OpenSslDigestNidEnum.SHA224, OpenSslEvpPkeyEnum.DSA), ] +SIGNATURE_ALGORITHMS_BAD_HASH_NIDS = {sa[0] for sa in SIGNATURE_ALGORITHMS_BAD_HASH} # Mail servers with an increased connection limit, # matched by substring matching on their hostname. From b13209625ca1d82d66088409033dd9dff5cb333a Mon Sep 17 00:00:00 2001 From: Sasha Romijn Date: Thu, 27 Aug 2026 12:43:48 +0200 Subject: [PATCH 3/3] Extra safeguard, minor comment tweak --- checks/tasks/tls/scans.py | 2 +- checks/tasks/tls/tls_constants.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/checks/tasks/tls/scans.py b/checks/tasks/tls/scans.py index 972ae91e9..8c4c3f1eb 100644 --- a/checks/tasks/tls/scans.py +++ b/checks/tasks/tls/scans.py @@ -867,7 +867,7 @@ def test_key_exchange_hash( """ Test key exchange hashes per NCSC 3.3.5. Note that this is not the certificate hash, or TLS cipher hash. - The initial test is phase_out+bad to keep happy path connections low. + The initial test is bad+phase_out to keep happy path connections low. """ probe_bad_phase_out = _test_connection_with_limited_sigalgs( server_connectivity_info, SIGNATURE_ALGORITHMS_BAD_HASH + SIGNATURE_ALGORITHMS_PHASE_OUT_HASH diff --git a/checks/tasks/tls/tls_constants.py b/checks/tasks/tls/tls_constants.py index c484d6aad..d592a8e2a 100644 --- a/checks/tasks/tls/tls_constants.py +++ b/checks/tasks/tls/tls_constants.py @@ -142,6 +142,9 @@ (OpenSslDigestNidEnum.SHA224, OpenSslEvpPkeyEnum.DSA), ] SIGNATURE_ALGORITHMS_BAD_HASH_NIDS = {sa[0] for sa in SIGNATURE_ALGORITHMS_BAD_HASH} +assert not SIGNATURE_ALGORITHMS_BAD_HASH_NIDS & { + sa[0] for sa in SIGNATURE_ALGORITHMS_PHASE_OUT_HASH +}, "no hash may be in both the bad and phase-out lists: test_key_exchange_hash tells them apart by hash alone" # Mail servers with an increased connection limit, # matched by substring matching on their hostname.