diff --git a/README.md b/README.md index d62754d..c9b5fb0 100644 --- a/README.md +++ b/README.md @@ -232,7 +232,7 @@ Firefox uses its own certificate store, so even after OS-level install you need > **Auto-install on startup:** When running in `apps_script` mode the proxy will automatically detect if the CA is not yet trusted and attempt to install it for you. If it succeeds you'll see a confirmation in the log; if it fails (e.g. needs administrator rights) it will print instructions. You can also run `python main.py --install-cert` at any time to (re-)install the certificate. -> **Uninstalling:** To remove the certificate from your system's trust stores, run `python main.py --uninstall-cert` or use `python start.bat --uninstall-cert` on Windows. This removes the certificate from all system trust stores and Firefox profiles. +> **Uninstalling:** To remove the certificate from your system's trust stores, run `python main.py --uninstall-cert` or use `start.bat --uninstall-cert` on Windows. This removes the certificate from all system trust stores and Firefox profiles. > ⚠️ **Security note:** This certificate only works locally on your machine. Don't share the `ca/` folder with anyone. If you want to start fresh, delete the `ca/` folder and the tool will generate a new one. diff --git a/README_FA.md b/README_FA.md index d404baf..1dff643 100644 --- a/README_FA.md +++ b/README_FA.md @@ -291,7 +291,7 @@ python3 main.py --disable-socks5 python3 main.py --log-level DEBUG python3 main.py -c /path/to/config.json python3 main.py --install-cert # نصب گواهی CA و خروج -python3 main.py --uninstall-cert # حذف گراهی CA و خروج +python3 main.py --uninstall-cert # حذف گواهی CA و خروج python3 main.py --no-cert-check # رد شدن از بررسی خودکار گواهی python3 main.py --scan # اسکن IP های Google و یافتن سریع‌ترین ``` diff --git a/main.py b/main.py index 08e56d3..5be59e8 100644 --- a/main.py +++ b/main.py @@ -108,6 +108,28 @@ def parse_args(): def main(): args = parse_args() + + # Handle cert-only commands before loading config so they can run standalone. + if args.install_cert or args.uninstall_cert: + setup_logging("INFO") + _log = logging.getLogger("Main") + + if args.install_cert: + _log.info("Installing CA certificate…") + if not os.path.exists(CA_CERT_FILE): + from mitm import MITMCertManager + MITMCertManager() # side-effect: creates ca/ca.crt + ca/ca.key + ok = install_ca(CA_CERT_FILE) + sys.exit(0 if ok else 1) + + _log.info("Removing CA certificate…") + ok = uninstall_ca(CA_CERT_FILE) + if ok: + _log.info("CA certificate removed successfully.") + else: + _log.warning("CA certificate removal may have failed. Check logs above.") + sys.exit(0 if ok else 1) + config_path = args.config try: @@ -195,24 +217,6 @@ def main(): print("Deploy the Apps Script from Code.gs and paste the Deployment ID.") sys.exit(1) - # ── Certificate installation ────────────────────────────────────────── - if args.install_cert: - setup_logging("INFO") - _log = logging.getLogger("Main") - _log.info("Installing CA certificate…") - ok = install_ca(CA_CERT_FILE) - sys.exit(0 if ok else 1) - - # ── Certificate uninstallation ─────────────────────────────────────────── - if args.uninstall_cert: - setup_logging("INFO") - _log = logging.getLogger("Main") - _log.info("Removing CA certificate…") - ok = uninstall_ca(CA_CERT_FILE) - if ok: - _log.info("CA certificate removed successfully.") - else: - _log.warning("CA certificate removal may have failed. Check logs above.") # ── Google IP Scanner ────────────────────────────────────────────────── if args.scan: setup_logging("INFO") diff --git a/src/cert_installer.py b/src/cert_installer.py index ffd1f75..704a4a0 100644 --- a/src/cert_installer.py +++ b/src/cert_installer.py @@ -383,8 +383,11 @@ def _uninstall_firefox(cert_name: str): for profile in profile_dirs: db = f"sql:{profile}" if os.path.exists(os.path.join(profile, "cert9.db")) else f"dbm:{profile}" try: - _run(["certutil", "-D", "-n", cert_name, "-d", db], check=False) - log.info("Removed from Firefox profile: %s", os.path.basename(profile)) + result = _run(["certutil", "-D", "-n", cert_name, "-d", db], check=False) + if result.returncode == 0: + log.info("Removed from Firefox profile: %s", os.path.basename(profile)) + else: + log.debug("Firefox profile %s: certificate not present", os.path.basename(profile)) except (subprocess.CalledProcessError, FileNotFoundError) as exc: log.debug("Firefox profile %s: %s", os.path.basename(profile), exc) @@ -393,11 +396,14 @@ def _uninstall_firefox(cert_name: str): # Uninstall functions # ───────────────────────────────────────────────────────────────────────────── -def _uninstall_windows(cert_name: str) -> bool: +def _uninstall_windows(cert_path: str, cert_name: str) -> bool: """Remove certificate from the Windows Trusted Root store.""" + thumbprint = _cert_thumbprint(cert_path) + # Try per-user store first (no admin required) try: - _run(["certutil", "-delstore", "-user", "Root", cert_name]) + target = thumbprint if thumbprint else cert_name + _run(["certutil", "-delstore", "-user", "Root", target]) log.info("Certificate removed from Windows user Trusted Root store.") return True except (subprocess.CalledProcessError, FileNotFoundError) as exc: @@ -405,7 +411,8 @@ def _uninstall_windows(cert_name: str) -> bool: # Try system store (requires admin) try: - _run(["certutil", "-delstore", "Root", cert_name]) + target = thumbprint if thumbprint else cert_name + _run(["certutil", "-delstore", "Root", target]) log.info("Certificate removed from Windows system Trusted Root store.") return True except (subprocess.CalledProcessError, FileNotFoundError) as exc: @@ -413,11 +420,20 @@ def _uninstall_windows(cert_name: str) -> bool: # Fallback: use PowerShell try: - ps_cmd = ( - f"Remove-Item -Path Cert:\\CurrentUser\\Root\\{cert_name} -Force -ErrorAction SilentlyContinue" - ) - _run(["powershell", "-NoProfile", "-Command", ps_cmd], check=False) - log.info("Attempted certificate removal via PowerShell.") + if thumbprint: + ps_cmd = ( + "Get-ChildItem Cert:\\CurrentUser\\Root | " + f"Where-Object {{ $_.Thumbprint -eq '{thumbprint}' }} | " + "Remove-Item -Force -ErrorAction SilentlyContinue" + ) + else: + ps_cmd = ( + "Get-ChildItem Cert:\\CurrentUser\\Root | " + f"Where-Object {{ $_.Subject -like '*CN={cert_name}*' -or $_.FriendlyName -eq '{cert_name}' }} | " + "Remove-Item -Force -ErrorAction SilentlyContinue" + ) + _run(["powershell", "-NoProfile", "-Command", ps_cmd]) + log.info("Certificate removal via PowerShell completed.") return True except (subprocess.CalledProcessError, FileNotFoundError) as exc: log.error("PowerShell removal failed: %s", exc) @@ -436,7 +452,7 @@ def _uninstall_macos(cert_name: str) -> bool: "security", "delete-certificate", "-c", cert_name, login_keychain, - ], check=False) + ]) log.info("Certificate removed from macOS login keychain.") return True except (subprocess.CalledProcessError, FileNotFoundError) as exc: @@ -448,7 +464,7 @@ def _uninstall_macos(cert_name: str) -> bool: "sudo", "security", "delete-certificate", "-c", cert_name, "/Library/Keychains/System.keychain", - ], check=False) + ]) log.info("Certificate removed from macOS system keychain.") return True except (subprocess.CalledProcessError, FileNotFoundError) as exc: @@ -582,7 +598,7 @@ def uninstall_ca(cert_path: str, cert_name: str = CERT_NAME) -> bool: log.info("Removing CA certificate from %s…", system) if system == "Windows": - ok = _uninstall_windows(cert_name) + ok = _uninstall_windows(cert_path, cert_name) elif system == "Darwin": ok = _uninstall_macos(cert_name) elif system == "Linux": diff --git a/start.bat b/start.bat index ce73afa..b6e4499 100644 --- a/start.bat +++ b/start.bat @@ -65,7 +65,8 @@ if not exist "config.json" ( ) REM -------- Check for uninstall flag -------- -if "%~1"=="--uninstall-cert" ( +echo %* | findstr /C:"--uninstall-cert" >nul +if not errorlevel 1 ( echo [*] Uninstalling CA certificate ... "%VPY%" main.py --uninstall-cert exit /b %errorlevel%