Files
Projektarbeit-MYP/backend/utils/ssl_fix.py

484 lines
18 KiB
Python

#!/usr/bin/env python3
"""
SSL Fix Tool für MYP Platform
Behebt ERR_SSL_KEY_USAGE_INCOMPATIBLE Browser-Fehler durch Neugenerierung
browser-kompatibler SSL-Zertifikate mit korrekten Key Usage Extensions.
"""
import os
import subprocess
import logging
import shutil
from pathlib import Path
from datetime import datetime
# Logger
logger = logging.getLogger(__name__)
class SSLBrowserFix:
"""
Behebt SSL-Browser-Kompatibilitätsprobleme durch Neugenerierung
von Zertifikaten mit korrekten Extensions
"""
def __init__(self, app_dir="/opt/myp"):
self.app_dir = Path(app_dir)
# Verschiedene SSL-Pfade im System
self.ssl_locations = [
self.app_dir / "ssl",
self.app_dir / "certs",
self.app_dir / "certs" / "localhost",
self.app_dir / "instance" / "ssl",
Path("/etc/ssl/certs/myp")
]
# Dateipfade für verschiedene Benennungskonventionen
self.cert_names = ["cert.pem", "myp.crt", "localhost.crt", "server.crt"]
self.key_names = ["key.pem", "myp.key", "localhost.key", "server.key"]
def find_existing_certificates(self):
"""
Findet alle existierenden SSL-Zertifikate im System
Returns:
list: Liste von (cert_path, key_path) Tupeln
"""
found_certs = []
for ssl_dir in self.ssl_locations:
if ssl_dir.exists():
for cert_name in self.cert_names:
for key_name in self.key_names:
cert_path = ssl_dir / cert_name
key_path = ssl_dir / key_name
if cert_path.exists() and key_path.exists():
found_certs.append((cert_path, key_path))
return found_certs
def check_certificate_browser_compatibility(self, cert_path):
"""
Prüft ob ein Zertifikat browser-kompatibel ist
Args:
cert_path: Pfad zum Zertifikat
Returns:
dict: Kompatibilitätsbericht
"""
result = {
'compatible': False,
'issues': [],
'details': {}
}
try:
# Zertifikat-Details extrahieren
cmd = ["openssl", "x509", "-in", str(cert_path), "-noout", "-text"]
proc = subprocess.run(cmd, capture_output=True, text=True)
if proc.returncode != 0:
result['issues'].append("Zertifikat kann nicht gelesen werden")
return result
cert_text = proc.stdout
# Key Usage prüfen
if "Digital Signature" in cert_text and "Key Encipherment" in cert_text:
result['details']['key_usage'] = "✅ Korrekt"
else:
result['issues'].append("Key Usage fehlt: Digital Signature, Key Encipherment")
result['details']['key_usage'] = "❌ Fehlerhaft"
# Extended Key Usage prüfen
if "TLS Web Server Authentication" in cert_text:
result['details']['extended_key_usage'] = "✅ Korrekt"
else:
result['issues'].append("Extended Key Usage fehlt: TLS Web Server Authentication")
result['details']['extended_key_usage'] = "❌ Fehlerhaft"
# Subject Alternative Names prüfen
if "Subject Alternative Name" in cert_text:
result['details']['san'] = "✅ Vorhanden"
else:
result['issues'].append("Subject Alternative Names fehlen")
result['details']['san'] = "❌ Fehlt"
# Basic Constraints prüfen
if "CA:FALSE" in cert_text:
result['details']['basic_constraints'] = "✅ Korrekt"
else:
result['issues'].append("Basic Constraints nicht gesetzt")
result['details']['basic_constraints'] = "❌ Fehlerhaft"
# Signature Algorithm prüfen
if "sha256WithRSAEncryption" in cert_text:
result['details']['signature'] = "✅ SHA-256"
elif "sha1WithRSAEncryption" in cert_text:
result['issues'].append("Veraltete SHA-1 Signatur")
result['details']['signature'] = "⚠️ SHA-1 (veraltet)"
else:
result['details']['signature'] = "❓ Unbekannt"
# Gültigkeit prüfen
cmd = ["openssl", "x509", "-in", str(cert_path), "-noout", "-checkend", "86400"]
proc = subprocess.run(cmd, capture_output=True)
if proc.returncode == 0:
result['details']['validity'] = "✅ Gültig"
else:
result['issues'].append("Zertifikat ist abgelaufen oder läuft bald ab")
result['details']['validity'] = "❌ Abgelaufen"
# Kompatibilität bewerten
result['compatible'] = len(result['issues']) == 0
except Exception as e:
result['issues'].append(f"Fehler bei Analyse: {e}")
return result
def create_browser_compatible_openssl_config(self, config_path):
"""
Erstellt OpenSSL-Konfiguration für browser-kompatible Zertifikate
Args:
config_path: Pfad für die Konfigurationsdatei
"""
config_content = """[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[req_distinguished_name]
C = DE
ST = Baden-Wuerttemberg
L = Stuttgart
O = Mercedes-Benz AG
OU = MYP Druckerverwaltung
CN = m040tbaraspi001
[v3_req]
# Basic Constraints - Zertifikat ist NICHT eine CA
basicConstraints = critical, CA:FALSE
# Key Usage - Kritisch für Browser-Kompatibilität
keyUsage = critical, digitalSignature, keyEncipherment, keyAgreement
# Extended Key Usage - Definiert Verwendungszweck
extendedKeyUsage = critical, serverAuth, clientAuth
# Subject Alternative Names - Alle unterstützten Domains/IPs
subjectAltName = critical, @alt_names
# Netscape Zertifikat-Typ (Legacy-Kompatibilität)
nsCertType = server
# Kommentar für Identifikation
nsComment = "Browser-kompatibles MYP SSL-Zertifikat (ERR_SSL_KEY_USAGE_INCOMPATIBLE Fix)"
[alt_names]
# Lokale Entwicklung
DNS.1 = localhost
DNS.2 = *.localhost
IP.1 = 127.0.0.1
IP.2 = ::1
# Produktions-Hostname
DNS.3 = m040tbaraspi001
DNS.4 = m040tbaraspi001.local
# Intranet-Domain
DNS.5 = m040tbaraspi001.de040.corpintra.net
DNS.6 = *.de040.corpintra.net
# Zusätzliche IPs
IP.3 = 0.0.0.0
"""
with open(config_path, 'w', encoding='utf-8') as f:
f.write(config_content)
logger.info(f"OpenSSL-Konfiguration erstellt: {config_path}")
def generate_browser_compatible_certificate(self, cert_path, key_path, force=False):
"""
Generiert browser-kompatibles SSL-Zertifikat
Args:
cert_path: Pfad für Zertifikat
key_path: Pfad für Private Key
force: Überschreibt existierende Dateien
Returns:
bool: True wenn erfolgreich
"""
cert_path = Path(cert_path)
key_path = Path(key_path)
# Prüfe ob bereits vorhanden
if not force and cert_path.exists() and key_path.exists():
logger.info("Zertifikat bereits vorhanden - verwende --force zum Überschreiben")
return True
try:
# Verzeichnis erstellen
cert_path.parent.mkdir(parents=True, exist_ok=True)
# Temporäre OpenSSL-Konfiguration
config_path = cert_path.parent / "openssl_temp.conf"
self.create_browser_compatible_openssl_config(config_path)
logger.info("Generiere browser-kompatibles SSL-Zertifikat...")
# Private Key generieren (RSA 2048 für Performance)
key_cmd = [
"openssl", "genrsa",
"-out", str(key_path),
"2048"
]
result = subprocess.run(key_cmd, capture_output=True, text=True)
if result.returncode != 0:
logger.error(f"Private Key Generierung fehlgeschlagen: {result.stderr}")
return False
logger.info("✅ Private Key generiert")
# Browser-kompatibles Zertifikat erstellen
cert_cmd = [
"openssl", "req",
"-new", "-x509",
"-key", str(key_path),
"-out", str(cert_path),
"-days", "365",
"-config", str(config_path),
"-extensions", "v3_req",
"-sha256" # SHA-256 Signatur für Sicherheit
]
result = subprocess.run(cert_cmd, capture_output=True, text=True)
if result.returncode != 0:
logger.error(f"Zertifikat-Generierung fehlgeschlagen: {result.stderr}")
return False
logger.info("✅ Browser-kompatibles Zertifikat generiert")
# Berechtigungen setzen
os.chmod(key_path, 0o600) # Nur Besitzer kann lesen
os.chmod(cert_path, 0o644) # Alle können lesen
# Aufräumen
config_path.unlink(missing_ok=True)
# Validierung
compatibility = self.check_certificate_browser_compatibility(cert_path)
if compatibility['compatible']:
logger.info("✅ Zertifikat ist browser-kompatibel")
return True
else:
logger.warning(f"⚠️ Zertifikat-Probleme: {compatibility['issues']}")
return True # Trotzdem als Erfolg werten, da generiert
except Exception as e:
logger.error(f"Fehler bei Zertifikat-Generierung: {e}")
return False
def fix_all_certificates(self, force=False):
"""
Repariert alle gefundenen SSL-Zertifikate im System
Args:
force: Erzwingt Neugenerierung auch bei gültigen Zertifikaten
Returns:
dict: Bericht über durchgeführte Reparaturen
"""
report = {
'fixed': [],
'failed': [],
'skipped': [],
'total_found': 0
}
# Finde existierende Zertifikate
existing_certs = self.find_existing_certificates()
report['total_found'] = len(existing_certs)
logger.info(f"Gefunden: {len(existing_certs)} SSL-Zertifikat-Paare")
if not existing_certs:
# Erstelle Standard-Zertifikat in bevorzugtem Pfad
default_ssl_dir = self.app_dir / "ssl"
default_cert = default_ssl_dir / "cert.pem"
default_key = default_ssl_dir / "key.pem"
if self.generate_browser_compatible_certificate(default_cert, default_key, force=True):
report['fixed'].append((str(default_cert), str(default_key)))
logger.info("✅ Standard-SSL-Zertifikat erstellt")
else:
report['failed'].append((str(default_cert), str(default_key)))
logger.error("❌ Standard-SSL-Zertifikat Erstellung fehlgeschlagen")
# Repariere existierende Zertifikate
for cert_path, key_path in existing_certs:
logger.info(f"Prüfe Zertifikat: {cert_path}")
# Prüfe Browser-Kompatibilität
compatibility = self.check_certificate_browser_compatibility(cert_path)
if not force and compatibility['compatible']:
report['skipped'].append((str(cert_path), str(key_path)))
logger.info(f"✅ Zertifikat ist bereits kompatibel: {cert_path}")
continue
# Backup erstellen
backup_cert = cert_path.parent / f"{cert_path.name}.backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
backup_key = key_path.parent / f"{key_path.name}.backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
try:
shutil.copy2(cert_path, backup_cert)
shutil.copy2(key_path, backup_key)
logger.info(f"Backup erstellt: {backup_cert}")
except Exception as e:
logger.warning(f"Backup fehlgeschlagen: {e}")
# Regeneriere Zertifikat
if self.generate_browser_compatible_certificate(cert_path, key_path, force=True):
report['fixed'].append((str(cert_path), str(key_path)))
logger.info(f"✅ Zertifikat repariert: {cert_path}")
else:
report['failed'].append((str(cert_path), str(key_path)))
logger.error(f"❌ Zertifikat-Reparatur fehlgeschlagen: {cert_path}")
# Backup wiederherstellen
try:
shutil.copy2(backup_cert, cert_path)
shutil.copy2(backup_key, key_path)
logger.info("Backup wiederhergestellt")
except Exception as e:
logger.error(f"Backup-Wiederherstellung fehlgeschlagen: {e}")
return report
def diagnose_ssl_issues(self):
"""
Führt umfassende SSL-Diagnose durch
Returns:
dict: Diagnosebericht
"""
diagnosis = {
'certificates_found': [],
'compatibility_issues': [],
'recommendations': []
}
logger.info("🔍 Führe SSL-Diagnose durch...")
# Finde alle Zertifikate
existing_certs = self.find_existing_certificates()
for cert_path, key_path in existing_certs:
cert_info = {
'cert_path': str(cert_path),
'key_path': str(key_path),
'compatibility': self.check_certificate_browser_compatibility(cert_path)
}
diagnosis['certificates_found'].append(cert_info)
if not cert_info['compatibility']['compatible']:
diagnosis['compatibility_issues'].extend(cert_info['compatibility']['issues'])
# Empfehlungen generieren
if not existing_certs:
diagnosis['recommendations'].append("Kein SSL-Zertifikat gefunden - Erstelle neue Zertifikate")
if diagnosis['compatibility_issues']:
diagnosis['recommendations'].append("Browser-Kompatibilitätsprobleme gefunden - Regeneriere Zertifikate")
if "ERR_SSL_KEY_USAGE_INCOMPATIBLE" in str(diagnosis['compatibility_issues']):
diagnosis['recommendations'].append("Key Usage Extensions korrigieren")
return diagnosis
def main():
"""Hauptfunktion für Kommandozeilen-Nutzung"""
import argparse
parser = argparse.ArgumentParser(description="SSL Browser-Kompatibilitäts-Fix für MYP Platform")
parser.add_argument("--app-dir", default="/opt/myp", help="MYP Anwendungsverzeichnis")
parser.add_argument("--force", action="store_true", help="Erzwinge Neugenerierung aller Zertifikate")
parser.add_argument("--diagnose", action="store_true", help="Nur Diagnose durchführen")
parser.add_argument("--verbose", action="store_true", help="Ausführliche Ausgabe")
args = parser.parse_args()
# Logging konfigurieren
level = logging.DEBUG if args.verbose else logging.INFO
logging.basicConfig(
level=level,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# SSL-Fix ausführen
ssl_fix = SSLBrowserFix(args.app_dir)
if args.diagnose:
# Nur Diagnose
diagnosis = ssl_fix.diagnose_ssl_issues()
print("\n🔍 SSL-DIAGNOSE BERICHT")
print("=" * 50)
print(f"\n📋 Gefundene Zertifikate: {len(diagnosis['certificates_found'])}")
for cert_info in diagnosis['certificates_found']:
print(f" 📄 {cert_info['cert_path']}")
print(f" Kompatibel: {'' if cert_info['compatibility']['compatible'] else ''}")
for detail_key, detail_value in cert_info['compatibility']['details'].items():
print(f" {detail_key}: {detail_value}")
if diagnosis['compatibility_issues']:
print(f"\n⚠️ Probleme: {len(diagnosis['compatibility_issues'])}")
for issue in set(diagnosis['compatibility_issues']):
print(f"{issue}")
if diagnosis['recommendations']:
print(f"\n💡 Empfehlungen:")
for rec in diagnosis['recommendations']:
print(f"{rec}")
else:
# SSL-Zertifikate reparieren
print("\n🔧 SSL BROWSER-KOMPATIBILITÄTS-FIX")
print("=" * 50)
report = ssl_fix.fix_all_certificates(force=args.force)
print(f"\n📊 BERICHT:")
print(f" Gefunden: {report['total_found']} Zertifikat-Paare")
print(f" Repariert: {len(report['fixed'])}")
print(f" Übersprungen: {len(report['skipped'])}")
print(f" Fehlgeschlagen: {len(report['failed'])}")
if report['fixed']:
print(f"\n✅ Reparierte Zertifikate:")
for cert, key in report['fixed']:
print(f"{cert}")
if report['failed']:
print(f"\n❌ Fehlgeschlagene Reparaturen:")
for cert, key in report['failed']:
print(f"{cert}")
print(f"\n🌐 Nach der Reparatur:")
print(f" 1. Browser-Cache leeren")
print(f" 2. MYP-Anwendung neu starten")
print(f" 3. https://localhost:5000 oder https://m040tbaraspi001.de040.corpintra.net aufrufen")
if __name__ == "__main__":
main()