import os import sys import json import shutil import sqlite3 import unittest from datetime import datetime, timezone, timedelta from cryptography import x509 from cryptography.x509.oid import NameOID from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import rsa # Ensure src/ is importable ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) SRC_DIR = os.path.join(ROOT_DIR, "src") if ROOT_DIR not in sys.path: sys.path.insert(0, ROOT_DIR) if SRC_DIR not in sys.path: sys.path.insert(0, SRC_DIR) import server_enrollment as enrollment import Server def _create_mock_signed_license_cert(csr_pem: str, seats: int = 50, license_id: str = "LIC-TEST-12345") -> tuple: """ Helper to generate a mock master CA and sign a sub-CA CSR with LOGAR license OIDs using pure cryptography library (without importing the private License_Authority submodule). """ # 1. Generate Root CA ca_key = rsa.generate_private_key(65537, 2048) ca_name = x509.Name([ x509.NameAttribute(NameOID.COUNTRY_NAME, "AT"), x509.NameAttribute(NameOID.ORGANIZATION_NAME, "LOGAR Master Authority"), x509.NameAttribute(NameOID.COMMON_NAME, "LOGAR-Master-Root-CA") ]) now = datetime.now(timezone.utc) ca_cert = ( x509.CertificateBuilder() .subject_name(ca_name) .issuer_name(ca_name) .public_key(ca_key.public_key()) .serial_number(x509.random_serial_number()) .not_valid_before(now - timedelta(days=1)) .not_valid_after(now + timedelta(days=3650)) .add_extension(x509.BasicConstraints(ca=True, path_length=1), critical=True) .sign(ca_key, hashes.SHA256()) ) ca_pem = ca_cert.public_bytes(serialization.Encoding.PEM).decode("utf-8") # 2. Parse CSR and sign Subordinate CA with custom OIDs csr = x509.load_pem_x509_csr(csr_pem.encode("utf-8")) sub_cert = ( x509.CertificateBuilder() .subject_name(csr.subject) .issuer_name(ca_name) .public_key(csr.public_key()) .serial_number(x509.random_serial_number()) .not_valid_before(now - timedelta(hours=1)) .not_valid_after(now + timedelta(days=365)) .add_extension(x509.BasicConstraints(ca=True, path_length=0), critical=True) .add_extension(x509.UnrecognizedExtension(enrollment.OID_LOGAR_SEATS, str(seats).encode("utf-8")), critical=False) .add_extension(x509.UnrecognizedExtension(enrollment.OID_LOGAR_LICENSE_ID, license_id.encode("utf-8")), critical=False) .add_extension(x509.UnrecognizedExtension(enrollment.OID_LOGAR_ISSUER_URL, b"https://logar.eibl.tech"), critical=False) .sign(ca_key, hashes.SHA256()) ) sub_pem = sub_cert.public_bytes(serialization.Encoding.PEM).decode("utf-8") return sub_pem, ca_pem class TestServerLicensingAndWebUI(unittest.TestCase): def setUp(self): self.test_dir = os.path.join(ROOT_DIR, "test_lic_tmp") os.makedirs(self.test_dir, exist_ok=True) self.server_cert_dir = os.path.join(self.test_dir, "server_certs") self.server_db = os.path.join(self.test_dir, "server_state.db") def tearDown(self): if os.path.exists(self.test_dir): shutil.rmtree(self.test_dir, ignore_errors=True) def test_ca_csr_and_lic_request_generation(self): """Verifies subordinate CA CSR and .lic request file generation on the server.""" lic_data, lic_json = enrollment.create_license_request_lic( cert_dir=self.server_cert_dir, license_server="https://logar.eibl.tech", server_fingerprint="AABBCCDDEEFF00112233", hostname="test-hub.corp.internal", requested_seats=30 ) self.assertEqual(lic_data["format"], "LOGAR_LICENSE_REQUEST") self.assertEqual(lic_data["license_server"], "https://logar.eibl.tech") self.assertEqual(lic_data["requested_seats"], 30) self.assertIn("BEGIN CERTIFICATE REQUEST", lic_data["csr"]) self.assertTrue(os.path.exists(os.path.join(self.server_cert_dir, "ca.csr"))) self.assertTrue(os.path.exists(os.path.join(self.server_cert_dir, "logar_server.lic"))) def test_license_info_extraction_and_custom_oids(self): """Verifies extraction of embedded custom seat OIDs and license attributes from certificates.""" csr_pem, _ = enrollment.generate_ca_csr(cert_dir=self.server_cert_dir) sub_pem, _ = _create_mock_signed_license_cert(csr_pem, seats=42, license_id="LIC-ABC-4242") info = enrollment.extract_license_info_from_cert(sub_pem) self.assertTrue(info["valid"]) self.assertEqual(info["seats"], 42) self.assertEqual(info["license_id"], "LIC-ABC-4242") self.assertEqual(info["issuer_url"], "https://logar.eibl.tech") self.assertFalse(info["is_expired"]) self.assertTrue(info["is_ca"]) def test_server_license_import_and_seat_update(self): """Verifies that importing a signed CA cert updates the server's seat quota in DB and memory.""" # 1. Generate CSR on server side csr_pem, _ = enrollment.generate_ca_csr(cert_dir=self.server_cert_dir) # 2. Mock sign it with 50 seats sub_pem, _ = _create_mock_signed_license_cert(csr_pem, seats=50, license_id="LIC-IMPORT-50") # 3. Setup mock server state and DB Server.init_db(self.server_db, max_seats=5) Server.SERVER_STATE["config"] = { "db_path": self.server_db, "cert_dir": self.server_cert_dir, "max_seats": 5, "tcp_host": "127.0.0.1", "tcp_port": 9443, "hermes_host": "127.0.0.1", "hermes_port": 8443, "server_fingerprint": "MOCK_FP", "server_name": "Test-Hub", "license_server_url": "https://logar.eibl.tech" } # 4. Extract summary before import summary_before = Server.get_license_summary() self.assertEqual(summary_before["max_seats"], 5) # 5. Save the signed CA certificate to cert_dir/ca.crt ca_path = os.path.join(self.server_cert_dir, "ca.crt") with open(ca_path, "w", encoding="utf-8") as f: f.write(sub_pem) # 6. Extract summary after import summary_after = Server.get_license_summary() self.assertEqual(summary_after["max_seats"], 50) self.assertEqual(summary_after["license_id"], "LIC-IMPORT-50") self.assertEqual(summary_after["status"], "ACTIVE") def test_server_client_revocation_and_reactivation(self): """Verifies revocation and reactivation of edge client seats on the server hub.""" Server.init_db(self.server_db, max_seats=2) Server.SERVER_STATE["config"] = { "db_path": self.server_db, "cert_dir": self.server_cert_dir, "max_seats": 2 } # Insert active clients conn = sqlite3.connect(self.server_db) conn.execute("INSERT INTO clients (client_id, hostname, os_type, cert_fingerprint, status) VALUES ('c1', 'win1', 'windows', 'FP1', 'active')") conn.execute("INSERT INTO clients (client_id, hostname, os_type, cert_fingerprint, status) VALUES ('c2', 'lin1', 'linux', 'FP2', 'active')") conn.commit() conn.close() # Revoke c1 req = Server.ClientActionRequest(client_id="c1") res = Server.revoke_client(req) self.assertEqual(res["status"], "revoked") summary = Server.get_license_summary() self.assertEqual(summary["active_seats"], 1) self.assertEqual(summary["revoked_seats"], 1) self.assertEqual(summary["available_seats"], 1) # Reactivate c1 res_react = Server.reactivate_client(req) self.assertEqual(res_react["status"], "active") summary2 = Server.get_license_summary() self.assertEqual(summary2["active_seats"], 2) self.assertEqual(summary2["available_seats"], 0) def test_activated_lic_package_handling(self): """Verifies that an activated .lic package structure can be processed and verified.""" csr_pem, _ = enrollment.generate_ca_csr(cert_dir=self.server_cert_dir) sub_pem, root_pem = _create_mock_signed_license_cert(csr_pem, seats=20, license_id="LIC-ACT-20") lic_package = { "format": "LOGAR_ACTIVATED_LICENSE", "version": "2.0.1", "license_id": "LIC-ACT-20", "seats": 20, "server_fingerprint": "FP_TEST_HUB", "ca_cert": sub_pem, "master_root_ca": root_pem, "days_valid": 365 } self.assertEqual(lic_package["seats"], 20) self.assertIn("BEGIN CERTIFICATE", lic_package["ca_cert"]) self.assertIn("BEGIN CERTIFICATE", lic_package["master_root_ca"]) # Verify certificate parses info = enrollment.extract_license_info_from_cert(lic_package["ca_cert"]) self.assertEqual(info["seats"], 20) self.assertEqual(info["license_id"], "LIC-ACT-20") def test_signature_verification_and_anti_forgery(self): """Verifies cryptographic signature validation against master root CA and detection of forged certificates.""" csr_pem, _ = enrollment.generate_ca_csr(cert_dir=self.server_cert_dir) sub_pem, root_pem = _create_mock_signed_license_cert(csr_pem, seats=30, license_id="LIC-VALID-30") # 1. Valid signature against genuine root CA self.assertTrue(enrollment.verify_certificate_signature(sub_pem, root_pem)) info_valid = enrollment.extract_license_info_from_cert(sub_pem, upstream_ca_pem=root_pem) self.assertTrue(info_valid["signature_verified"]) self.assertEqual(info_valid["seats"], 30) # 2. Fake / Rogue Root CA trying to verify the same cert fake_csr, _ = enrollment.generate_ca_csr(cert_dir=self.server_cert_dir) _, fake_root_pem = _create_mock_signed_license_cert(fake_csr, seats=999, license_id="LIC-FAKE") self.assertFalse(enrollment.verify_certificate_signature(sub_pem, fake_root_pem)) info_invalid = enrollment.extract_license_info_from_cert(sub_pem, upstream_ca_pem=fake_root_pem) self.assertFalse(info_invalid["signature_verified"]) def test_database_tampering_seat_reconciliation(self): """Verifies that direct DB manipulation of max_seats is reconciled against the certified X.509 quota.""" from license_manager import LicenseManager csr_pem, _ = enrollment.generate_ca_csr(cert_dir=self.server_cert_dir) sub_pem, root_pem = _create_mock_signed_license_cert(csr_pem, seats=25, license_id="LIC-SEAT-25") # Save certified ca.crt and upstream_ca.crt ca_path = os.path.join(self.server_cert_dir, "ca.crt") with open(ca_path, "w", encoding="utf-8") as f: f.write(sub_pem) with open(os.path.join(self.server_cert_dir, "upstream_ca.crt"), "w", encoding="utf-8") as f: f.write(root_pem) Server.init_db(self.server_db, max_seats=25) server_state = { "config": { "db_path": self.server_db, "cert_dir": self.server_cert_dir, "max_seats": 25 } } # Attacker attempts direct SQLite manipulation: SET max_seats = 99999 conn = sqlite3.connect(self.server_db) conn.execute("UPDATE license_config SET max_seats = 99999 WHERE id = 1") conn.commit() conn.close() # LicenseManager inspects state and auto-reconciles max_seats to certified 25 summary = LicenseManager.get_license_summary(server_state=server_state, db_path=self.server_db) self.assertEqual(summary["max_seats"], 25) self.assertTrue(summary["signature_verified"]) # Check DB was reconciled conn = sqlite3.connect(self.server_db) row = conn.execute("SELECT max_seats FROM license_config WHERE id = 1").fetchone() conn.close() self.assertEqual(row[0], 25) def test_import_license_signature_validation(self): """Verifies that /api/license/import rejects certificates not signed by the upstream authority.""" from fastapi import HTTPException csr_pem, _ = enrollment.generate_ca_csr(cert_dir=self.server_cert_dir) sub_pem, root_pem = _create_mock_signed_license_cert(csr_pem, seats=50, license_id="LIC-MOCK-50") # Generate mismatched root CA fake_csr, _ = enrollment.generate_ca_csr(cert_dir=self.server_cert_dir) _, rogue_root_pem = _create_mock_signed_license_cert(fake_csr, seats=10, license_id="LIC-ROGUE") Server.init_db(self.server_db, max_seats=5) Server.SERVER_STATE["config"] = { "db_path": self.server_db, "cert_dir": self.server_cert_dir, "max_seats": 5 } # Attempt to import sub_pem with mismatched upstream_ca -> must raise HTTPException 400 req_bad = Server.LicenseImportRequest(ca_cert=sub_pem, upstream_ca=rogue_root_pem) with self.assertRaises(HTTPException) as ctx: Server.import_signed_license(req_bad, auth_user="admin") self.assertEqual(ctx.exception.status_code, 400) self.assertIn("Cryptographic verification failed", ctx.exception.detail) # Import sub_pem with valid matching upstream_ca -> must succeed req_good = Server.LicenseImportRequest(ca_cert=sub_pem, upstream_ca=root_pem) res = Server.import_signed_license(req_good, auth_user="admin") self.assertEqual(res["status"], "success") self.assertEqual(res["seats"], 50) def test_webui_rendering(self): """Verifies that the WebUI HTML contains key components, stats, and download triggers.""" Server.init_db(self.server_db, max_seats=10) Server.SERVER_STATE["config"] = { "db_path": self.server_db, "cert_dir": self.server_cert_dir, "max_seats": 10, "tcp_port": 9443, "hermes_port": 8443, "server_name": "Test-Cloud-Hub", "server_fingerprint": "1234567890ABCDEF", "license_server_url": "https://logar.eibl.tech" } def test_auto_update_lock_and_package_upload_protection(self): """Verifies that unlicensed servers block package upload with 403 and render locked UI.""" from fastapi import HTTPException Server.init_db(self.server_db, max_seats=0) Server.SERVER_STATE["config"] = { "db_path": self.server_db, "cert_dir": self.server_cert_dir, "max_seats": 0 } # Ensure license_config has no auto_update conn = sqlite3.connect(self.server_db) conn.execute("UPDATE license_config SET license_status = 'unlicensed', features = '[]' WHERE id = 1") conn.commit() conn.close() # WebUI should render locked banner and disabled upload controls resp = Server.serve_webui() html = resp.body.decode("utf-8") self.assertIn("Fleet Auto-Update is Inactive & Locked", html) self.assertIn("disabled", html) def test_json_lic_package_import(self): """Verifies importing an activated .lic JSON package via /api/license/import.""" csr_pem, _ = enrollment.generate_ca_csr(cert_dir=self.server_cert_dir) sub_pem, root_pem = _create_mock_signed_license_cert( csr_pem, seats=30, license_id="LIC-TEST-JSON-01" ) Server.init_db(self.server_db, max_seats=5) Server.SERVER_STATE["config"] = { "db_path": self.server_db, "cert_dir": self.server_cert_dir, "max_seats": 5 } # JSON package format lic_pkg = { "format": "LOGAR_LICENSE_PACKAGE", "license_id": "LIC-TEST-JSON-01", "seats": 30, "features": ["enterprise_db", "auto_update"], "ca_cert": sub_pem, "master_root_ca": root_pem } req = Server.LicenseImportRequest(lic=json.dumps(lic_pkg)) res = Server.import_signed_license(req, auth_user="admin") self.assertEqual(res["status"], "success") self.assertEqual(res["seats"], 30) if __name__ == "__main__": unittest.main()