umbau 1.3.2
This commit is contained in:
@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user