Skip to content

Commit d16e7cc

Browse files
committed
Update client initiated renegotiation limits, incl DB changes for 3 states now, also requires new labels see categories.py
1 parent eefaaea commit d16e7cc

File tree

7 files changed

+168
-40
lines changed

7 files changed

+168
-40
lines changed

checks/categories.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from typing import Optional
44

55
from checks import scoring
6+
from checks.models import TLSClientInitiatedRenegotiationStatus
67
from checks.scoring import (
78
ORDERED_STATUSES,
89
STATUS_ERROR,
@@ -1183,14 +1184,27 @@ def __init__(self):
11831184
model_score_field="client_reneg_score",
11841185
)
11851186

1186-
def result_good(self):
1187+
def save_result(self, status: TLSClientInitiatedRenegotiationStatus):
1188+
handlers = {
1189+
TLSClientInitiatedRenegotiationStatus.not_allowed: self.result_not_allowed,
1190+
TLSClientInitiatedRenegotiationStatus.allowed_with_low_limit: self.result_allowed_with_low_limit,
1191+
TLSClientInitiatedRenegotiationStatus.allowed_with_too_high_limit: self.result_allowed_with_too_high_limit,
1192+
}
1193+
return handlers[status]()
1194+
1195+
def result_not_allowed(self):
11871196
self._status(STATUS_SUCCESS)
1188-
self.verdict = "detail web tls renegotiation-client verdict good"
1197+
self.verdict = "detail web tls renegotiation-client verdict not-allowed"
11891198
self.tech_data = "detail tech data no"
11901199

1191-
def result_bad(self):
1200+
def result_allowed_with_low_limit(self):
1201+
self._status(STATUS_INFO)
1202+
self.verdict = "detail web tls renegotiation-client verdict allowed-with-low-limit"
1203+
self.tech_data = "detail tech data phase-out"
1204+
1205+
def result_allowed_with_too_high_limit(self):
11921206
self._status(STATUS_FAIL)
1193-
self.verdict = "detail web tls renegotiation-client verdict bad"
1207+
self.verdict = "detail web tls renegotiation-client verdict allowed-with-too-high-limit"
11941208
self.tech_data = "detail tech data yes"
11951209

11961210

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Generated by Django 4.2.22 on 2025-07-22 18:17
2+
from enum import Enum
3+
4+
import checks.models
5+
from django.db import migrations
6+
import enumfields.fields
7+
8+
9+
class TLSClientInitiatedRenegotiationStatus(Enum):
10+
not_allowed = 1
11+
allowed_with_low_limit = 2
12+
allowed_with_too_high_limit = 3
13+
14+
15+
def set_client_reneg_new(apps, schema_editor):
16+
DomainTestTls = apps.get_model("checks", "DomainTestTls")
17+
DomainTestTls.object.update(client_reneg_new=TLSClientInitiatedRenegotiationStatus.not_allowed)
18+
DomainTestTls.object.filter(dtls__client_reneg=True).update(
19+
client_reneg_new=TLSClientInitiatedRenegotiationStatus.allowed_with_too_high_limit
20+
)
21+
22+
23+
def set_client_reneg_old(apps, schema_editor):
24+
DomainTestTls = apps.get_model("checks", "DomainTestTls")
25+
DomainTestTls.object.update(client_reneg=False)
26+
DomainTestTls.object.filter(
27+
client_reneg_new=TLSClientInitiatedRenegotiationStatus.allowed_with_too_high_limit
28+
).update(client_reneg=True)
29+
30+
31+
class Migration(migrations.Migration):
32+
dependencies = [
33+
("checks", "0018_domaintesttls_caa_records"),
34+
]
35+
36+
operations = [
37+
migrations.AddField(
38+
model_name="domaintesttls",
39+
name="client_reneg_new",
40+
field=enumfields.fields.EnumField(
41+
enum=checks.models.TLSClientInitiatedRenegotiationStatus,
42+
max_length=10,
43+
null=True,
44+
),
45+
),
46+
migrations.RunPython(set_client_reneg_new, set_client_reneg_old),
47+
migrations.RemoveField(
48+
model_name="domaintesttls",
49+
name="client_reneg",
50+
),
51+
migrations.RenameField(
52+
model_name="domaintesttls",
53+
old_name="client_reneg_new",
54+
new_name="client_reneg",
55+
),
56+
]

checks/models.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ class OcspStatus(Enum):
8989
not_in_cert = 3
9090

9191

92+
class TLSClientInitiatedRenegotiationStatus(Enum):
93+
not_allowed = 1
94+
allowed_with_low_limit = 2
95+
allowed_with_too_high_limit = 3
96+
97+
9298
class ZeroRttStatus(Enum):
9399
bad = 0
94100
good = 1
@@ -524,7 +530,7 @@ class DomainTestTls(BaseTestModel):
524530
compression_score = models.IntegerField(null=True)
525531
secure_reneg = models.BooleanField(null=True, default=False)
526532
secure_reneg_score = models.IntegerField(null=True)
527-
client_reneg = models.BooleanField(null=True, default=False)
533+
client_reneg = EnumField(TLSClientInitiatedRenegotiationStatus, null=True)
528534
client_reneg_score = models.IntegerField(null=True)
529535

530536
zero_rtt = EnumField(ZeroRttStatus, default=ZeroRttStatus.bad)

checks/scoring.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@
144144
WEB_TLS_SECURE_RENEG_WORST_STATUS = STATUS_FAIL
145145

146146
WEB_TLS_CLIENT_RENEG_GOOD = FULL_WEIGHT_POINTS
147+
WEB_TLS_CLIENT_RENEG_OK = FULL_WEIGHT_POINTS
147148
WEB_TLS_CLIENT_RENEG_BAD = NO_POINTS
148149
WEB_TLS_CLIENT_RENEG_WORST_STATUS = STATUS_INFO
149150

checks/tasks/tls/evaluation.py

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,23 @@
44
from cryptography.hazmat._oid import AuthorityInformationAccessOID, ExtensionOID
55
from cryptography.x509 import AuthorityInformationAccess, ExtensionNotFound
66
from nassl.ephemeral_key_info import EcDhEphemeralKeyInfo, DhEphemeralKeyInfo, OpenSslEvpPkeyEnum
7-
from sslyze import TlsVersionEnum, CipherSuiteAcceptedByServer, CipherSuite, CertificateDeploymentAnalysisResult
7+
from sslyze import (
8+
TlsVersionEnum,
9+
CipherSuiteAcceptedByServer,
10+
CipherSuite,
11+
CertificateDeploymentAnalysisResult,
12+
SessionRenegotiationScanResult,
13+
)
814
from sslyze.plugins.openssl_cipher_suites.cipher_suites import _TLS_1_3_CIPHER_SUITES
915

1016
from checks import scoring
11-
from checks.models import KexHashFuncStatus, CipherOrderStatus, OcspStatus, KexRSAPKCSStatus
17+
from checks.models import (
18+
KexHashFuncStatus,
19+
CipherOrderStatus,
20+
OcspStatus,
21+
KexRSAPKCSStatus,
22+
TLSClientInitiatedRenegotiationStatus,
23+
)
1224
from checks.tasks.tls.tls_constants import (
1325
PROTOCOLS_GOOD,
1426
PROTOCOLS_SUFFICIENT,
@@ -244,6 +256,57 @@ def score(self) -> scoring.Score:
244256
)
245257

246258

259+
@dataclass(frozen=True)
260+
class TLSRenegotiationEvaluation:
261+
"""
262+
Evaluate the secure renegotiation settings per NCSC 3.4.2
263+
"""
264+
265+
supports_secure_renegotiation: bool
266+
client_renegotiations_success_count: int
267+
268+
# What counts as "limited" per NCSC 3.4.2
269+
MAX_SECURE_RENEG_ATTEMPTS = 10
270+
# The number of attempts the scan should make
271+
SCAN_RENEGOTIATION_LIMIT = MAX_SECURE_RENEG_ATTEMPTS + 1
272+
273+
@classmethod
274+
def from_session_renegotiation_scan_result(cls, session_renegotiation_scan_result: SessionRenegotiationScanResult):
275+
return cls(
276+
supports_secure_renegotiation=session_renegotiation_scan_result.supports_secure_renegotiation,
277+
client_renegotiations_success_count=session_renegotiation_scan_result.client_renegotiations_success_count,
278+
)
279+
280+
@property
281+
def status_secure_renegotiation(self) -> bool:
282+
return self.supports_secure_renegotiation
283+
284+
@property
285+
def status_client_initiated_renegotiation(self) -> TLSClientInitiatedRenegotiationStatus:
286+
if not self.client_renegotiations_success_count:
287+
return TLSClientInitiatedRenegotiationStatus.not_allowed
288+
if self.client_renegotiations_success_count <= self.MAX_SECURE_RENEG_ATTEMPTS:
289+
return TLSClientInitiatedRenegotiationStatus.allowed_with_low_limit
290+
return TLSClientInitiatedRenegotiationStatus.allowed_with_too_high_limit
291+
292+
@property
293+
def score_secure_renegotiation(self) -> scoring.Score:
294+
return (
295+
scoring.WEB_TLS_SECURE_RENEG_GOOD
296+
if self.supports_secure_renegotiation
297+
else scoring.WEB_TLS_SECURE_RENEG_BAD
298+
)
299+
300+
@property
301+
def score_client_initiated_renegotiation(self) -> scoring.Score:
302+
scores = {
303+
TLSClientInitiatedRenegotiationStatus.not_allowed: scoring.WEB_TLS_CLIENT_RENEG_GOOD,
304+
TLSClientInitiatedRenegotiationStatus.allowed_with_low_limit: scoring.WEB_TLS_CLIENT_RENEG_OK,
305+
TLSClientInitiatedRenegotiationStatus.allowed_with_too_high_limit: scoring.WEB_TLS_CLIENT_RENEG_BAD,
306+
}
307+
return scores[self.status_client_initiated_renegotiation]
308+
309+
247310
@dataclass(frozen=True)
248311
class KeyExchangeRSAPKCSFunctionEvaluation:
249312
"""

checks/tasks/tls/scans.py

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
TLSCipherOrderEvaluation,
7474
TLSOCSPEvaluation,
7575
KeyExchangeRSAPKCSFunctionEvaluation,
76+
TLSRenegotiationEvaluation,
7677
)
7778
from checks.tasks.tls.tls_constants import (
7879
CERT_SIGALG_GOOD,
@@ -621,6 +622,9 @@ def check_mail_tls(result: ServerScanResult, all_suites: List[CipherSuitesScanAt
621622
key_exchange_rsa_pkcs_evaluation = test_key_exchange_rsa_pkcs(server_conn_info)
622623
key_exchange_hash_evaluation = test_key_exchange_hash(server_conn_info)
623624

625+
renegotiation_evaluation = TLSRenegotiationEvaluation.from_session_renegotiation_scan_result(
626+
result.scan_result.session_renegotiation.result
627+
)
624628
cert_results = cert_checks(result.server_location.hostname, ChecksMode.MAIL)
625629

626630
# HACK for DANE-TA(2) and hostname mismatch!
@@ -642,18 +646,10 @@ def check_mail_tls(result: ServerScanResult, all_suites: List[CipherSuitesScanAt
642646
cipher_order_score=cipher_order_evaluation.score,
643647
cipher_order=cipher_order_evaluation.status,
644648
cipher_order_violation=cipher_order_evaluation.violation,
645-
secure_reneg=result.scan_result.session_renegotiation.result.supports_secure_renegotiation,
646-
secure_reneg_score=(
647-
scoring.WEB_TLS_SECURE_RENEG_GOOD
648-
if result.scan_result.session_renegotiation.result.supports_secure_renegotiation
649-
else scoring.WEB_TLS_SECURE_RENEG_BAD
650-
),
651-
client_reneg=result.scan_result.session_renegotiation.result.is_vulnerable_to_client_renegotiation_dos,
652-
client_reneg_score=(
653-
scoring.WEB_TLS_CLIENT_RENEG_BAD
654-
if result.scan_result.session_renegotiation.result.is_vulnerable_to_client_renegotiation_dos
655-
else scoring.WEB_TLS_CLIENT_RENEG_GOOD
656-
),
649+
secure_reneg=renegotiation_evaluation.status_secure_renegotiation,
650+
secure_reneg_score=renegotiation_evaluation.score_secure_renegotiation,
651+
client_reneg=renegotiation_evaluation.status_client_initiated_renegotiation,
652+
client_reneg_score=renegotiation_evaluation.score_client_initiated_renegotiation,
657653
compression=result.scan_result.tls_compression.result.supports_compression
658654
if result.scan_result.tls_compression.result
659655
else None,
@@ -750,6 +746,9 @@ def check_web_tls(url, af_ip_pair=None, *args, **kwargs):
750746
)
751747
key_exchange_rsa_pkcs_evaluation = test_key_exchange_rsa_pkcs(server_conn_info)
752748
key_exchange_hash_evaluation = test_key_exchange_hash(server_conn_info)
749+
renegotiation_evaluation = TLSRenegotiationEvaluation.from_session_renegotiation_scan_result(
750+
result.scan_result.session_renegotiation.result
751+
)
753752

754753
ocsp_evaluation = TLSOCSPEvaluation.from_certificate_deployments(
755754
result.scan_result.certificate_info.result.certificate_deployments[0]
@@ -768,18 +767,10 @@ def check_web_tls(url, af_ip_pair=None, *args, **kwargs):
768767
cipher_order_score=cipher_order_evaluation.score,
769768
cipher_order=cipher_order_evaluation.status,
770769
cipher_order_violation=cipher_order_evaluation.violation,
771-
secure_reneg=result.scan_result.session_renegotiation.result.supports_secure_renegotiation,
772-
secure_reneg_score=(
773-
scoring.WEB_TLS_SECURE_RENEG_GOOD
774-
if result.scan_result.session_renegotiation.result.supports_secure_renegotiation
775-
else scoring.WEB_TLS_SECURE_RENEG_BAD
776-
),
777-
client_reneg=result.scan_result.session_renegotiation.result.is_vulnerable_to_client_renegotiation_dos,
778-
client_reneg_score=(
779-
scoring.WEB_TLS_CLIENT_RENEG_BAD
780-
if result.scan_result.session_renegotiation.result.is_vulnerable_to_client_renegotiation_dos
781-
else scoring.WEB_TLS_CLIENT_RENEG_GOOD
782-
),
770+
secure_reneg=renegotiation_evaluation.status_secure_renegotiation,
771+
secure_reneg_score=renegotiation_evaluation.score_secure_renegotiation,
772+
client_reneg=renegotiation_evaluation.status_client_initiated_renegotiation,
773+
client_reneg_score=renegotiation_evaluation.score_client_initiated_renegotiation,
783774
compression=result.scan_result.tls_compression.result.supports_compression,
784775
compression_score=(
785776
scoring.WEB_TLS_COMPRESSION_BAD
@@ -820,7 +811,10 @@ def run_sslyze(
820811
This threading is handled inside sslyze.
821812
"""
822813
log.debug(f"starting sslyze scan for {[scan.server_location for scan in scans]}")
823-
scanner = Scanner(per_server_concurrent_connections_limit=connection_limit, concurrent_server_scans_limit=10)
814+
scanner = Scanner(
815+
per_server_concurrent_connections_limit=connection_limit,
816+
concurrent_server_scans_limit=TLSRenegotiationEvaluation.SCAN_RENEGOTIATION_LIMIT,
817+
)
824818
scanner.queue_scans(scans)
825819
for result in scanner.get_results():
826820
log.debug(f"sslyze scan for {result.server_location} result: {result.scan_status}")

checks/tasks/tls/tasks_reports.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -467,10 +467,7 @@ def annotate_and_combine_all(good_items, sufficient_items, bad_items, phaseout_i
467467
else:
468468
category.subtests["renegotiation_secure"].result_bad()
469469

470-
if dttls.client_reneg:
471-
category.subtests["renegotiation_client"].result_bad()
472-
else:
473-
category.subtests["renegotiation_client"].result_good()
470+
category.subtests["renegotiation_client"].save_result(dttls.client_reneg)
474471

475472
if not dttls.cert_chain:
476473
category.subtests["cert_trust"].result_could_not_test()
@@ -628,10 +625,7 @@ def annotate_and_combine_all(good_items, sufficient_items, bad_items, phaseout_i
628625
else:
629626
category.subtests["renegotiation_secure"].result_bad()
630627

631-
if dttls.client_reneg:
632-
category.subtests["renegotiation_client"].result_bad()
633-
else:
634-
category.subtests["renegotiation_client"].result_good()
628+
category.subtests["renegotiation_client"].save_result(dttls.client_reneg)
635629

636630
if not dttls.cert_chain:
637631
category.subtests["cert_trust"].result_could_not_test()

0 commit comments

Comments
 (0)