fix: check_llm_certs.py — handle DER files, openssl timeout 20s, binary PEM reads

This commit is contained in:
2026-05-28 23:10:18 +03:00
parent 05f42d5489
commit 8a815ab1b1

View File

@@ -60,8 +60,8 @@ def _lib_ver(name: str) -> str:
def _count_certs(path: str) -> int: def _count_certs(path: str) -> int:
try: try:
with open(path) as f: with open(path, "rb") as f:
return f.read().count("-----BEGIN CERTIFICATE-----") return f.read().count(b"-----BEGIN CERTIFICATE-----")
except Exception: except Exception:
return 0 return 0
@@ -69,31 +69,31 @@ def _count_certs(path: str) -> int:
def _cert_info(path: Path) -> dict: def _cert_info(path: Path) -> dict:
import subprocess as sp import subprocess as sp
info = {"file": str(path), "format": path.suffix} info = {"file": str(path), "format": path.suffix}
r = sp.run(
["openssl", "x509", "-in", str(path), "-noout", def _try_read(fmt: str) -> dict | None:
"-subject", "-issuer", "-fingerprint", "-sha256"], args = ["openssl", "x509", "-in", str(path), "-noout",
capture_output=True, text=True, timeout=5, "-subject", "-issuer", "-fingerprint", "-sha256"]
) if fmt == "DER":
if r.returncode == 0: args += ["-inform", "DER"]
r = sp.run(args, capture_output=True, text=True, timeout=5)
if r.returncode != 0:
return None
res = {}
for line in r.stdout.strip().split("\n"): for line in r.stdout.strip().split("\n"):
if line.startswith("subject="): info["subject"] = line[8:] if line.startswith("subject="): res["subject"] = line[8:]
elif line.startswith("issuer="): info["issuer"] = line[7:] elif line.startswith("issuer="): res["issuer"] = line[7:]
elif line.startswith("SHA256 Fingerprint="): info["fingerprint"] = line[19:] elif line.startswith("SHA256 Fingerprint="): res["fingerprint"] = line[19:]
else: return res
r2 = sp.run(["openssl", "x509", "-in", str(path), "-inform", "DER", "-noout", "-subject"],
capture_output=True, text=True, timeout=5) d = _try_read("PEM") or _try_read("DER") or {}
if r2.returncode == 0: info["subject"] = d.get("subject", "UNKNOWN_FORMAT")
info["subject"] = r2.stdout.strip().replace("subject=", "") info["issuer"] = d.get("issuer", "?")
info["format"] += " (DER)" info["fingerprint"] = d.get("fingerprint", "?")
else: info["format"] += " (DER)" if d is not None and _try_read("PEM") is None else ""
info["subject"] = "UNKNOWN_FORMAT" fp = info["fingerprint"]
info.setdefault("subject", "?")
info.setdefault("issuer", "?")
info.setdefault("fingerprint", "?")
fp = info.get("fingerprint", "")
if fp and fp != "?" and CA_BUNDLE.exists(): if fp and fp != "?" and CA_BUNDLE.exists():
with open(CA_BUNDLE) as f: with open(CA_BUNDLE, "rb") as f:
info["in_bundle"] = "yes" if fp in f.read() else "no" info["in_bundle"] = "yes" if fp.encode() in f.read() else "no"
else: else:
info["in_bundle"] = "unknown" info["in_bundle"] = "unknown"
return info return info
@@ -118,7 +118,7 @@ def _run(cmd: list[str], timeout: int = 15) -> tuple[int, str, str]:
except FileNotFoundError: except FileNotFoundError:
return -1, "", "command not found" return -1, "", "command not found"
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
return -2, "", "timeout" return -2, "", f"timeout ({timeout}s)"
def _make_ctx_cafile(): def _make_ctx_cafile():
@@ -184,7 +184,7 @@ def _diagnose(s: dict) -> str:
v = str(s.get(k, "")) v = str(s.get(k, ""))
issues.append(f"{k}={'OK' if '0 (ok)' in v else 'FAIL'}") issues.append(f"{k}={'OK' if '0 (ok)' in v else 'FAIL'}")
for lib in ("httpx", "requests"): for lib in ("httpx", "requests"):
for method in ("true", "cafile", "capath", "false"): for method in ("true", "cafile", "cafile_capath", "capath", "false"):
v = str(s.get(f"{lib}_{method}", "")) v = str(s.get(f"{lib}_{method}", ""))
issues.append(f"{lib}_{method}={'OK' if v.startswith('OK') else 'FAIL'}") issues.append(f"{lib}_{method}={'OK' if v.startswith('OK') else 'FAIL'}")
# Recommendation # Recommendation
@@ -250,33 +250,43 @@ def main() -> None:
results["custom_certs_count"] = len(custom_certs) results["custom_certs_count"] = len(custom_certs)
_section("3. OpenSSL s_client") _section("3. OpenSSL s_client")
# Note: if behind a corporate proxy, openssl s_client may hang.
# Adding -verify_return_error and -msg for better diagnostics.
base_cmd = ["openssl", "s_client", "-connect", f"{host}:443",
"-servername", host, "-verify_return_error"]
for label, args in [ for label, args in [
("openssl_default", []), ("openssl_default", []),
("openssl_cafile", ["-CAfile", str(CA_BUNDLE)]), ("openssl_cafile", ["-CAfile", str(CA_BUNDLE)]),
("openssl_capath", ["-CApath", str(CA_DIR)]), ("openssl_capath", ["-CApath", str(CA_DIR)]),
]: ]:
cmd = ["openssl", "s_client", "-connect", f"{host}:443", "-servername", host] + args cmd = base_cmd + args
rc, out, err = _run(cmd, 15) rc, out, err = _run(cmd, 20)
_parse_openssl(label, rc, out, err) _parse_openssl(label, rc, out, err)
# Chain test: combine all custom certs into one bundle # Chain test: combine PEM certs into one bundle (skip DER files)
cert_files = [] cert_files = []
for d in CUSTOM_DIRS: for d in CUSTOM_DIRS:
if d.is_dir(): if d.is_dir():
for f in d.iterdir(): for f in d.iterdir():
if f.suffix in (".pem", ".crt"): if f.suffix in (".pem", ".crt") and f.is_file():
cert_files.append(f) # Check if PEM
rc, _, _ = _run(["openssl", "x509", "-in", str(f), "-noout"], 3)
if rc == 0:
cert_files.append(f)
if len(cert_files) >= 3: if len(cert_files) >= 3:
with tempfile.NamedTemporaryFile(mode="w", suffix=".pem", delete=False) as tf: with tempfile.NamedTemporaryFile(mode="wb", suffix=".pem", delete=False) as tf:
chain_path = tf.name chain_path = tf.name
for name in ["RUSAL_ROOT", "Policy_CA", "RGM_Issuing"]: for name in ["RUSAL_ROOT", "Policy_CA", "RGM_Issuing"]:
for f in cert_files: for f in cert_files:
if name in f.name: if name in f.name:
tf.write(f.read_text()) tf.write(f.read_bytes())
break break
rc, out, err = _run(["openssl", "s_client", "-connect", f"{host}:443", "-CAfile", chain_path, "-servername", host], 15) rc, out, err = _run(["openssl", "s_client", "-connect", f"{host}:443",
"-CAfile", chain_path, "-servername", host], 20)
os.unlink(chain_path) os.unlink(chain_path)
_parse_openssl("openssl_chain", rc, out, err) _parse_openssl("openssl_chain", rc, out, err)
else:
print(" openssl_chain: ⚠ < 3 PEM certs found, skipping")
_section("4. Python httpx (как LLMClient)") _section("4. Python httpx (как LLMClient)")
_test_httpx("httpx_true", verify=True) _test_httpx("httpx_true", verify=True)