#!/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()