# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference) # Canonical Residual: https://corpusfetch.com/c/which-tls-cipher-or-certificate-negotiation-failures-must-be-logged-before-exit.md # Remnant Warrant: https://corpusfetch.com/c/which-tls-cipher-or-certificate-negotiation-failures-must-be-logged-before-exit.warrant.json # Markdown SHA-256: b7afe9744bc6beafea0579390fc85850f78a8e784ed9bdce7711782c6c7af102 # Code AST SHA-256: 55148a4bd563b9b37387ad276d1e910ddad6f21e31da04c2e80e7d43635c316b # Verified Classes: TLSVerdict, CertVerifyStatus, HandshakeObservation, SecurityBaseline, TLSManifest, TLSNegotiationGuard # Defensive Guards: 1 fail-closed invariants (zero external deps) from dataclasses import dataclass from enum import Enum, auto import hashlib import json class TLSVerdict(Enum): SECURE_NEGOTIATION = auto() HARD_FAILURE = auto() SILENT_DOWNGRADE_DETECTED = auto() CERTIFICATE_UNTRUSTED = auto() class CertVerifyStatus(Enum): VALID = auto() EXPIRED = auto() SELF_SIGNED = auto() UNTRUSTED_CA = auto() HOSTNAME_MISMATCH = auto() @dataclass(frozen=True) class HandshakeObservation: endpoint_id: str protocol_version: str cipher_suite: str cert_fingerprint: str cert_status: CertVerifyStatus connection_succeeded: bool @dataclass(frozen=True) class SecurityBaseline: min_protocol_version: str require_aead_cipher: bool @dataclass(frozen=True) class TLSManifest: verdict: TLSVerdict violations: tuple[str, ...] directive: str manifest_digest: str class TLSNegotiationGuard: """ Evaluates TLS handshake parameters to detect silent downgrades and certificate validation failures before session exit. """ # Order of protocol versions from weakest to strongest PROTOCOL_ORDER = ("TLSv1.0", "TLSv1.1", "TLSv1.2", "TLSv1.3") # Simple heuristic for AEAD cipher detection (GCM, CCM, CHACHA20) AEAD_MARKERS = ("GCM", "CCM", "CHACHA20", "POLY1305") @staticmethod def _hash(data: str) -> str: return hashlib.sha256(data.encode("utf-8")).hexdigest() @classmethod def _protocol_rank(cls, version: str) -> int: try: return cls.PROTOCOL_ORDER.index(version) except ValueError: return -1 @classmethod def evaluate_handshake( cls, obs: HandshakeObservation, baseline: SecurityBaseline ) -> TLSManifest: # Gate 1: Hard failure if not obs.connection_succeeded: return TLSManifest( verdict=TLSVerdict.HARD_FAILURE, violations=("CONNECTION_ABORTED",), directive="HALT: TLS handshake failed. Upstream unavailable.", manifest_digest=cls._hash(f"HARD:{obs.endpoint_id}"), ) violations: list[str] = [] # Gate 2: Certificate trust verification if obs.cert_status is not CertVerifyStatus.VALID: violations.append(f"CERT_{obs.cert_status.name}") # Gate 3: Protocol version downgrade obs_rank = cls._protocol_rank(obs.protocol_version) baseline_rank = cls._protocol_rank(baseline.min_protocol_version) if obs_rank < baseline_rank: violations.append( f"PROTOCOL_DOWNGRADE: {obs.protocol_version} < {baseline.min_protocol_version}" ) # Gate 4: Cipher suite forward secrecy check if baseline.require_aead_cipher: if not any(marker in obs.cipher_suite for marker in cls.AEAD_MARKERS): violations.append( f"CIPHER_DEGRADATION: {obs.cipher_suite} lacks AEAD/PFS" ) # Determine final verdict if any(v.startswith("CERT_") for v in violations): verdict = TLSVerdict.CERTIFICATE_UNTRUSTED directive = "HALT: Do not transmit sensitive data. Cert validation failed." elif violations: verdict = TLSVerdict.SILENT_DOWNGRADE_DETECTED directive = "HALT: Negotiated parameters below baseline. Rotate endpoint or reconfigure TLS." else: verdict = TLSVerdict.SECURE_NEGOTIATION directive = "PROCEED: Connection meets security baseline." digest_payload = { "endpoint": obs.endpoint_id, "verdict": verdict.name, "protocol": obs.protocol_version, "cipher": obs.cipher_suite, "violations": tuple(sorted(violations)), } digest = cls._hash( json.dumps(digest_payload, sort_keys=True, separators=(",", ":")) ) return TLSManifest( verdict=verdict, violations=tuple(violations), directive=directive, manifest_digest=digest, ) @classmethod def assert_channel_secure(cls, manifest: TLSManifest) -> None: """ Fail-closed enforcement: raises if the TLS channel is not secure. """ if manifest.verdict is not TLSVerdict.SECURE_NEGOTIATION: raise RuntimeError( f"TLS CHANNEL INSECURE: {manifest.verdict.name}. " f"Violations: {list(manifest.violations)}. {manifest.directive}" )