umbau 1.3.2

This commit is contained in:
2026-09-03 10:34:01 +02:00
parent b2c72de069
commit 0728a9af27
7 changed files with 392 additions and 53 deletions

View File

@ -47,7 +47,9 @@ import argparse
import socket
import ssl
import struct
import subprocess
import sys
import tempfile
import warnings
# ssl.TLSVersion.TLSv1/TLSv1_1 sind in neueren Python-Fassungen als veraltet
@ -207,6 +209,64 @@ def negotiate(sock: socket.socket, requested: int) -> int:
return selected
# Letztes erfolgreich abgeholtes Serverzertifikat (DER) -- fuer die Auswertung
# am Ende. Nicht schoen als Modulvariable, aber dieses Skript ist ein
# Diagnosewerkzeug mit genau einem Durchlauf, kein Baustein zum Wiederverwenden.
_ZERTIFIKAT: dict[str, bytes] = {}
def beschreibe_zertifikat(der: bytes) -> list[str]:
"""Eckdaten des Serverzertifikats -- zur Information, NICHT als Ursache.
Wichtige Einordnung (in einem Gegentest am 3.9. nachgeprueft): das
Signaturverfahren des Zertifikats ist hier NICHT der Ausloeser. Die
Zertifikatspruefung ist ausgeschaltet (wie bei ignore-cert), und ein
selbstsigniertes Zertifikat, dessen Signatur niemand prueft, kommt auch mit
SHA-1 und Sicherheitsstufe 2 anstandslos durch -- gegengeprueft gegen einen
eigens dafuer aufgesetzten Server mit SHA-1-Zertifikat.
Was bei einem Abbruch, der erst unterhalb von Sicherheitsstufe 1
verschwindet, tatsaechlich passiert, ist eine Stufe subtiler: OpenSSL 3.0
hat SHA-1 als SIGNATURVERFAHREN ab Stufe 1 verboten und nimmt es deshalb
nicht mehr in die signature_algorithms-Erweiterung des ClientHello auf.
Bei TLS 1.2 mit ECDHE muss der Server aber den ServerKeyExchange mit einem
Verfahren signieren, das der Client angeboten hat. Kann das Windows-Ziel
nur mit SHA-1 signieren -- etwa weil in der SCHANNEL-Registry unter
"Hashes\\SHA256" Enabled=0 steht oder eine Haertungsvorlage die neueren
Hashverfahren abgeschaltet hat --, findet es nichts Verwendbares und legt
auf. SCHANNEL schickt dabei ein TCP-RST statt einer sauberen
handshake_failure-Warnung: genau das "Connection reset by peer" oben.
Diese Angaben helfen trotzdem beim Einordnen des Ziels (Alter, Herkunft,
Schluessellaenge des Zertifikats).
"""
with tempfile.NamedTemporaryFile(suffix=".der") as fh:
fh.write(der)
fh.flush()
try:
ausgabe = subprocess.run(
["openssl", "x509", "-inform", "DER", "-in", fh.name, "-noout", "-text"],
capture_output=True, text=True, timeout=15,
).stdout
except (OSError, subprocess.SubprocessError) as exc:
return [f" (Zertifikat nicht auswertbar: {exc})"]
sig = kex = subject = None
for zeile in ausgabe.splitlines():
z = zeile.strip()
if z.startswith("Signature Algorithm:") and sig is None:
sig = z.split(":", 1)[1].strip()
elif z.startswith("Public-Key:") and kex is None:
kex = z.split(":", 1)[1].strip()
elif z.startswith("Subject:") and subject is None:
subject = z.split(":", 1)[1].strip()
return [
f" Inhaber: {subject or '(unbekannt)'}",
f" Schluessel: {kex or '(unbekannt)'}",
f" Signaturverfahren: {sig or '(unbekannt)'}",
" (nur zur Einordnung -- bei abgeschalteter Pruefung nicht die Fehlerursache)",
]
def try_handshake(host, port, requested, variante: Variante, timeout):
try:
with socket.create_connection((host, port), timeout=timeout) as sock:
@ -215,6 +275,7 @@ def try_handshake(host, port, requested, variante: Variante, timeout):
return (False, "Server waehlt 'Native RDP' -- kein TLS auf dieser Stufe")
with variante.context().wrap_socket(sock, server_hostname=host) as tls:
cert = tls.getpeercert(binary_form=True)
_ZERTIFIKAT["der"] = cert
return (True, f"{tls.version()}, {tls.cipher()[0]}, Zertifikat {len(cert)} Byte")
except ssl.SSLError as exc:
grund = getattr(exc, "reason", None) or exc
@ -281,6 +342,11 @@ def main() -> int:
gesprochen.append(v.label.replace("nur ", ""))
print(f" [{'OK ' if ok else 'FEHL'}] {v.label}: {text}")
if _ZERTIFIKAT.get("der"):
print("\n== Schritt 4: das vom Ziel vorgelegte Zertifikat ==")
for zeile in beschreibe_zertifikat(_ZERTIFIKAT["der"]):
print(zeile)
print("\n== Befund ==")
if ergebnisse["system"]:
print(" Der TLS-Handshake gelingt bereits mit den System-Vorgaben.")
@ -309,8 +375,18 @@ def main() -> int:
gezeigt = '""' if value == "" else (f'"{value}"' if isinstance(value, str) else value)
print(f" {key}: {gezeigt}")
print("\n Das wirkt NUR auf den guacd-Prozess (Environment=OPENSSL_CONF in dessen")
print(" Unit), nicht systemweit. Die saubere Loesung bleibt, das Windows-Ziel")
print(" passend zu konfigurieren -- dies ist die Ueberbrueckung bis dahin.")
print(" Unit), nicht systemweit. 'DEFAULT' schliesst auch bei Stufe 0 weiterhin")
print(" NULL- und anonyme Cipher aus -- es wird also keine ungeschuetzte")
print(" Verbindung moeglich, nur eine aeltere.")
if not ergebnisse.get("sec1") and not ergebnisse.get("sec1_max12"):
print("\n Dass erst Stufe 0 hilft (Stufe 1 UND 2 scheitern gleich), deutet auf die")
print(" signature_algorithms-Erweiterung hin: OpenSSL 3.0 bietet ab Stufe 1 kein")
print(" SHA-1 mehr an, und das Ziel kann den ServerKeyExchange offenbar nicht")
print(" anders signieren. Am Windows-Ziel gegenpruefen (PowerShell, Administrator):")
print(" Get-ItemProperty 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\SecurityProviders"
"\\SCHANNEL\\Hashes\\SHA256' -ErrorAction SilentlyContinue")
print(" Steht dort Enabled = 0, ist das die Wurzel. SHA256/SHA384 wieder")
print(" aktivieren, neu starten -- danach guacd_tls_legacy_compat auf false.")
return 1