diff --git a/src/Win_Client.py b/src/Win_Client.py index d8c8c9b..894db8c 100644 --- a/src/Win_Client.py +++ b/src/Win_Client.py @@ -24,17 +24,46 @@ CONFIG_FILE_NAME = "client_config.json" STATE_FILE_NAME = "client_state.json" -def enroll_client_if_needed(hub_url: str, enrollment_secret: str, cert_dir: str, client_id: str, hostname: str, os_type: str = "windows"): - """Bootstraps client enrollment if certificates are missing.""" +def is_cert_expiring_soon(cert_path: str, threshold_days: int = 30) -> bool: + """Checks if client certificate at cert_path is expiring within threshold_days.""" + if not os.path.exists(cert_path): + return True + try: + from cryptography import x509 + with open(cert_path, "r", encoding="utf-8") as f: + cert = x509.load_pem_x509_certificate(f.read().encode("utf-8")) + expiry = getattr(cert, "not_valid_after_utc", None) + if expiry is None: + expiry = cert.not_valid_after.replace(tzinfo=timezone.utc) + now = datetime.now(timezone.utc) + return expiry <= (now + timedelta(days=threshold_days)) + except Exception: + return True + + +def enroll_client_if_needed( + hub_url: str, + enrollment_secret: str, + cert_dir: str, + client_id: str, + hostname: str, + os_type: str = "windows", + force_renew: bool = False, + threshold_days: int = 30 +): + """Bootstraps client enrollment if certificates are missing or expiring soon.""" os.makedirs(cert_dir, exist_ok=True) ca_path = os.path.join(cert_dir, "ca.crt") cert_path = os.path.join(cert_dir, "client.crt") key_path = os.path.join(cert_dir, "client.key") - if os.path.exists(ca_path) and os.path.exists(cert_path) and os.path.exists(key_path): - return True + if not force_renew and os.path.exists(ca_path) and os.path.exists(cert_path) and os.path.exists(key_path): + if not is_cert_expiring_soon(cert_path, threshold_days=threshold_days): + return True + print(f"[*] Client certificate at {cert_path} is expiring within {threshold_days} days. Auto-renewing...") - print(f"[*] Bootstrapping client enrollment with LOGAR Hub at {hub_url}...") + action_name = "re-enrolling" if os.path.exists(cert_path) else "enrolling" + print(f"[*] Bootstrapping client {action_name} with LOGAR Hub at {hub_url}...") enroll_endpoint = f"{hub_url.rstrip('/')}/api/client/enroll" payload = { "client_id": client_id, @@ -64,7 +93,7 @@ def enroll_client_if_needed(hub_url: str, enrollment_secret: str, cert_dir: str, except Exception: pass - print(f"[+] Client enrolled successfully! Certificates saved to {os.path.abspath(cert_dir)}") + print(f"[+] Client certificates updated successfully in {os.path.abspath(cert_dir)}") return True @@ -269,6 +298,7 @@ def send_encrypted_logs_over_socket(config: dict, logs: list): machine_id = get_machine_identifier() # Attempt automatic enrollment bootstrap if certs are missing and secret is provided + hub_url = None if enrollment_secret: hermes_host = config.get("hermes_host", server_host) hermes_port = config.get("hermes_port", 8443) @@ -285,7 +315,22 @@ def send_encrypted_logs_over_socket(config: dict, logs: list): if has_mtls_certs: print(f"[*] Connecting to LOGAR server at {server_host}:{server_port} over mTLS (TLS 1.3)...") - with get_tls_socket(server_host, server_port, cert_dir) as sock: + sock = None + try: + sock = get_tls_socket(server_host, server_port, cert_dir) + except (ssl.SSLError, ssl.CertificateError, ConnectionResetError) as tls_err: + if enrollment_secret and hub_url: + print(f"[!] TLS handshake error ({tls_err}). Re-enrolling with LOGAR Hub...") + try: + enroll_client_if_needed(hub_url, enrollment_secret, cert_dir, machine_id, machine_id, os_type="windows", force_renew=True) + sock = get_tls_socket(server_host, server_port, cert_dir) + except Exception as retry_err: + print(f"[!] Re-enrollment or reconnection retry failed: {retry_err}") + raise + else: + raise + + with sock: payload = { "server": machine_id, "timestamp": datetime.now(timezone.utc).isoformat(),