#!/usr/bin/env python3 """ SSL Certificate Diagnostics for ss-tools. Tests all 4 layers with the actual libraries used in production. Key finding from ADR-0009: use capath, NOT cafile — OpenSSL 3.x ignores intermediate CA certs in cafile (verify code 20) but correctly builds chains with capath (verify code 0). Output: human-readable + JSON summary with AI diagnosis hint. """ from __future__ import annotations import argparse import json import os import platform import subprocess import sys from pathlib import Path # ── Constants ── CA_BUNDLE = Path("/etc/ssl/certs/ca-certificates.crt") CA_DIR = Path("/etc/ssl/certs") CUSTOM_DIRS = [ Path("/usr/local/share/ca-certificates/custom"), Path("/usr/local/share/ca-certificates/llm"), ] NSS_DB = f"sql:{Path(os.environ.get('HOME', '/root')) / '.pki' / 'nssdb'}" results: dict = {} # ── Helpers ── def _run(cmd: list[str], timeout: int = 15, input_data: str = "") -> tuple[int, str, str]: """Run subprocess with optional stdin.""" try: r = subprocess.run(cmd, input=input_data, capture_output=True, text=True, timeout=timeout) return r.returncode, r.stdout, r.stderr except FileNotFoundError: return -1, "", "command not found" except subprocess.TimeoutExpired: return -2, "", f"timeout ({timeout}s)" def _count_certs(path: str) -> int: try: with open(path, "rb") as f: return f.read().count(b"-----BEGIN CERTIFICATE-----") except Exception: return 0 # ── Openssl tests ── def test_openssl(label: str, extra_args: list[str]) -> None: """Test openssl s_client with empty stdin to prevent hang.""" cmd = ["openssl", "s_client", "-connect", f"{host}:443", "-servername", host, "-verify_return_error"] + extra_args rc, out, err = _run(cmd, timeout=20, input_data="") for line in out.split("\n"): if "Verify return code" in line: ok = "0 (ok)" in line results[f"openssl_{label}"] = f"{'OK' if ok else 'FAIL'}: {line.strip()}" print(f" openssl_{label}: {'✅' if ok else '❌'} {line.strip()}") return results[f"openssl_{label}"] = f"ERROR: {err.strip() or 'no output'}" print(f" openssl_{label}: ❌ {results[f'openssl_{label}']}") # ── Python library tests ── def test_httpx(key: str, verify) -> None: try: import httpx r = httpx.get(TARGET_URL, verify=verify, timeout=10) results[key] = "OK" print(f" {key}: ✅ status={r.status_code}") except Exception as e: results[key] = f"FAIL: {type(e).__name__}" print(f" {key}: ❌ {type(e).__name__}") def test_requests(key: str, verify) -> None: try: import requests as req r = req.get(TARGET_URL, verify=verify, timeout=10) results[key] = "OK" print(f" {key}: ✅ status={r.status_code}") except Exception as e: results[key] = f"FAIL: {type(e).__name__}" print(f" {key}: ❌ {type(e).__name__}") # ══════════════════════════════════════════════ # MAIN # ══════════════════════════════════════════════ if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--target", default=os.environ.get("TARGET_URL", "https://lite.ai.rusal.com")) args = parser.parse_args() TARGET_URL = args.target host = TARGET_URL.replace("https://", "").split("/")[0] LLM_SSL_VERIFY = os.environ.get("LLM_SSL_VERIFY", "true") # ── 1. System ── print("\n=== 1. System ===") print(f" target={TARGET_URL} host={host}") print(f" platform={platform.platform()}") print(f" python={sys.version.split()[0]}") for lib in ("httpx", "requests", "openai", "certifi"): try: mod = __import__(lib) v = getattr(mod, "__version__", "installed") p = getattr(mod, "__file__", "") if lib == "certifi": print(f" {lib}={v} path={mod.where()} certs={_count_certs(mod.where())}") else: print(f" {lib}={v}") except ImportError: print(f" {lib}=NOT_INSTALLED") # ── 2. System CA Store ── print("\n=== 2. System CA Store ===") bundle_certs = _count_certs(str(CA_BUNDLE)) if CA_BUNDLE.exists() else 0 hash_links = len(list(CA_DIR.glob("[0-9a-f]*.[0-9]"))) if CA_DIR.is_dir() else 0 print(f" bundle={CA_BUNDLE} certs={bundle_certs}") print(f" capath={CA_DIR} hash_links={hash_links}") print(" custom certs:") for d in CUSTOM_DIRS: if not d.is_dir(): continue for f in sorted(d.iterdir()): if f.suffix in (".pem", ".crt") and f.is_file(): rc, out, _ = _run(["openssl", "x509", "-in", str(f), "-noout", "-subject", "-issuer"], timeout=3) if rc != 0: rc, out, _ = _run(["openssl", "x509", "-in", str(f), "-inform", "DER", "-noout", "-subject"], timeout=3) subj = out.replace("subject=", "").strip() if rc == 0 else "UNREADABLE" print(f" {f.parent.name}/{f.name} subj={subj[:60]}") # ── 3. OpenSSL s_client (3 variants) ── print("\n=== 3. OpenSSL s_client ===") test_openssl("default", []) test_openssl("cafile", ["-CAfile", str(CA_BUNDLE)]) test_openssl("capath", ["-CApath", str(CA_DIR)]) # ── 4. Python httpx ── print("\n=== 4. Python httpx (LLMClient) ===") try: import httpx test_httpx("httpx_True", verify=True) try: import ssl ctx_cafile = ssl.create_default_context(cafile=str(CA_BUNDLE)) if CA_BUNDLE.exists() else ssl.create_default_context() ctx_capath = ssl.create_default_context(capath=str(CA_DIR)) if CA_DIR.is_dir() else ssl.create_default_context() test_httpx("httpx_cafile", verify=ctx_cafile) test_httpx("httpx_capath", verify=ctx_capath) except Exception as e: print(f" SSLContext: ❌ {e}") test_httpx("httpx_False", verify=False) except ImportError: print(" httpx: NOT_INSTALLED") # ── 5. Python requests ── print("\n=== 5. Python requests (translate plugin) ===") try: import requests as req test_requests("requests_True", verify=True) test_requests("requests_cafile", verify=str(CA_BUNDLE)) test_requests("requests_capath", verify=str(CA_DIR)) test_requests("requests_False", verify=False) except ImportError: print(" requests: NOT_INSTALLED") # ── 6. NSS Chromium ── print("\n=== 6. NSS Chromium ===") rc, out, err = _run(["certutil", "-L", "-d", NSS_DB], timeout=5) if rc == 0: lines = [l for l in out.split("\n") if l.strip() and "Certificate Nickname" not in l] print(f" DB={NSS_DB} certs={len(lines)}") for l in lines: if any(x in l.lower() for x in ("rusal", "llm-", "custom-")): print(f" {l}") else: print(f" NSS: {err.strip() or 'unavailable'}") # ── 7. LLM_SSL_VERIFY env ── print("\n=== 7. LLM_SSL_VERIFY ===") print(f" LLM_SSL_VERIFY={LLM_SSL_VERIFY} disabled={LLM_SSL_VERIFY.strip().lower() in ('false','0','no','off')}") # ── 8. JSON Summary ── print("\n=== 8. JSON Summary ===") summary = {k: results[k] for k in [ "openssl_default", "openssl_cafile", "openssl_capath", "httpx_True", "httpx_cafile", "httpx_capath", "httpx_False", "requests_True", "requests_cafile", "requests_capath", "requests_False", ] if k in results} # Diagnosis diag = [] for k in ("openssl_default", "openssl_cafile", "openssl_capath"): v = str(summary.get(k, "")) diag.append(f"{k}={'OK' if v.startswith('OK') else 'FAIL'}") for k in sorted(summary): if k.startswith("httpx") or k.startswith("requests"): v = str(summary.get(k, "")) diag.append(f"{k}={'OK' if v.startswith('OK') else 'FAIL'}") if str(results.get("httpx_capath", "")).startswith("OK") and not str(results.get("httpx_cafile", "")).startswith("OK"): diag.append("VERDICT=capath_works_cafile_fails_use_capath") elif str(summary.get("httpx_False", "")).startswith("OK"): diag.append("VERDICT=all_ssl_fails_verify_disabled_works") elif str(summary.get("openssl_default", "")).startswith("OK"): diag.append("VERDICT=all_ok") else: diag.append("VERDICT=needs_investigation") summary["diagnosis"] = "; ".join(diag) print(json.dumps(summary, indent=2))