## tests/test_security_event_logging.nim ## Comprehensive tests for security event logging system (Task 11.1d) import std/[unittest, times, json, os, strutils, options] import ../src/nimpak/security/event_logger import ../src/nimpak/security/revocation_manager import ../src/nimpak/cli/audit_commands suite "Security Event Logging System": let testLogPath = "/tmp/nip_test_security.log" let testCasPath = "/tmp/nip_test_cas" let testCrlPath = "/tmp/nip_test_crl" setup: # Clean up test files if fileExists(testLogPath): removeFile(testLogPath) if dirExists(testCasPath): removeDir(testCasPath) if dirExists(testCrlPath): removeDir(testCrlPath) createDir(testCasPath) createDir(testCrlPath) teardown: # Clean up test files if fileExists(testLogPath): removeFile(testLogPath) if dirExists(testCasPath): removeDir(testCasPath) if dirExists(testCrlPath): removeDir(testCrlPath) test "Security Event Logger Creation": let logger = newSecurityEventLogger(testLogPath, testCasPath) check: logger.logPath == testLogPath logger.casStore == testCasPath logger.signingKey.isNone() logger.lastEventHash == "" logger.eventCounter == 0 test "Security Event Creation": let event = createSecurityEvent( EventKeyRevocation, SeverityCritical, "test-source", "Test revocation event", %*{"key_id": "test-key-123"} ) check: event.eventType == EventKeyRevocation event.severity == SeverityCritical event.source == "test-source" event.message == "Test revocation event" event.metadata["key_id"].getStr() == "test-key-123" event.hashChainPrev == "" event.hashChainCurrent == "" event.signature.isNone() test "Event Hash Calculation": let event = createSecurityEvent( EventSignatureVerification, SeverityInfo, "verifier", "Signature verified", %*{"package": "test-package"} ) let hash = calculateEventHash(event) check: hash.startsWith("blake3-") hash.len > 10 test "Security Event Logging": var logger = newSecurityEventLogger(testLogPath, testCasPath) var event = createSecurityEvent( EventPackageVerification, SeverityInfo, "package-manager", "Package verified successfully", %*{"package": "htop", "version": "3.2.1"} ) logger.logSecurityEvent(event) check: fileExists(testLogPath) logger.eventCounter == 1 logger.lastEventHash == event.hashChainCurrent event.hashChainPrev == "" # First event has no previous hash event.hashChainCurrent != "" test "Hash Chain Continuity": var logger = newSecurityEventLogger(testLogPath, testCasPath) # Log first event var event1 = createSecurityEvent(EventKeyGeneration, SeverityInfo, "key-manager", "Key generated") logger.logSecurityEvent(event1) # Log second event var event2 = createSecurityEvent(EventKeyRevocation, SeverityWarning, "key-manager", "Key revoked") logger.logSecurityEvent(event2) check: event1.hashChainPrev == "" event2.hashChainPrev == event1.hashChainCurrent event2.hashChainCurrent != event1.hashChainCurrent logger.eventCounter == 2 test "Revocation Event Logging": var logger = newSecurityEventLogger(testLogPath, testCasPath) let revocation = RevocationEvent( keyId: "test-key-456", reason: ReasonKeyCompromise, reasonText: "Key compromised in security incident", revocationDate: now().utc(), supersededBy: some("test-key-789"), affectedPackages: @["package1", "package2"], emergencyRevocation: true, responseActions: @["immediate_crl_update", "package_re_signing"] ) logger.logKeyRevocation(revocation) check: fileExists(testLogPath) logger.eventCounter == 1 test "Emergency Revocation Logging": var logger = newSecurityEventLogger(testLogPath, testCasPath) logger.logEmergencyRevocation("emergency-key-123", "Suspected compromise", @["critical-package"]) check: fileExists(testLogPath) logger.eventCounter == 1 test "Key Rollover Logging": var logger = newSecurityEventLogger(testLogPath, testCasPath) let rollover = RolloverEvent( oldKeyId: "old-key-123", newKeyId: "new-key-456", rolloverType: "scheduled", overlapPeriod: "30d", affectedRepositories: @["stable", "testing"], validationResults: %*{"packages_re_signed": 150, "errors": []} ) logger.logKeyRollover(rollover) check: fileExists(testLogPath) logger.eventCounter == 1 test "Signature Verification Logging": var logger = newSecurityEventLogger(testLogPath, testCasPath) # Log successful verification logger.logSignatureVerification("test-package", "key-123", true) # Log failed verification logger.logSignatureVerification("bad-package", "key-456", false, "Invalid signature") check: fileExists(testLogPath) logger.eventCounter == 2 test "Trust Violation Logging": var logger = newSecurityEventLogger(testLogPath, testCasPath) logger.logTrustViolation("suspicious-package", "Untrusted key used", "untrusted-key-789") check: fileExists(testLogPath) logger.eventCounter == 1 test "CRL Update Logging": var logger = newSecurityEventLogger(testLogPath, testCasPath) logger.logCRLUpdate("https://crl.example.com/nexus.crl", @["revoked-key-1", "revoked-key-2"], true) check: fileExists(testLogPath) logger.eventCounter == 1 test "Security Incident Logging": var logger = newSecurityEventLogger(testLogPath, testCasPath) logger.logSecurityIncident( "key_compromise", "Multiple keys compromised in coordinated attack", @["repository-server", "signing-server"], @["revoke_all_keys", "regenerate_infrastructure", "notify_users"] ) check: fileExists(testLogPath) logger.eventCounter == 1 suite "Revocation Manager": let testCrlPath = "/tmp/nip_test_crl" let testCasPath = "/tmp/nip_test_cas" let testLogPath = "/tmp/nip_test_security.log" setup: if dirExists(testCrlPath): removeDir(testCrlPath) if dirExists(testCasPath): removeDir(testCasPath) if fileExists(testLogPath): removeFile(testLogPath) createDir(testCrlPath) createDir(testCasPath) teardown: if dirExists(testCrlPath): removeDir(testCrlPath) if dirExists(testCasPath): removeDir(testCasPath) if fileExists(testLogPath): removeFile(testLogPath) test "Revocation Manager Creation": let logger = newSecurityEventLogger(testLogPath, testCasPath) let manager = newRevocationManager(testC, testCasPath, logger) check: manager.crlPath == testCrlPath manager.casStore == testCasPath manager.distributionUrls.len == 0 manager.policies.len == 0 test "Default Rollover Policies": let policies = getDefaultPolicies() check: policies.hasKey("ed25519") policies.hasKey("dilithium") policies["ed25519"].algorithm == "ed25519" policies["ed25519"].quantumResistant == false policies["dilithium"].quantumResistant == true test "Emergency Revocation": let logger = newSecurityEventLogger(testLogPath, testCasPath) var manager = newRevocationManager(testCrlPath, testCasPath, logger) let result = manager.emergencyRevocation( "compromised-key-123", "Key compromised in security breach", @["critical-package-1", "critical-package-2"] ) check: result.isOk() fileExists(testCrlPath / "revocation_list.nexus") test "Scheduled Key Rollover": let logger = newSecurityEventLogger(testLogPath, testCasPath) var manager = newRevocationManager(testCrlPath, testCasPath, logger) # Set up rollover policy let policy = RolloverPolicy( algorithm: "ed25519", keySize: 256, overlapPeriod: initDuration(days = 30), gracePeriod: initDuration(days = 7), autoRolloverInterval: initDuration(days = 365), emergencyRolloverEnabled: true, quantumResistant: false ) manager.setRolloverPolicy("ed25519", policy) let result = manager.scheduleKeyRollover("old-key-123", "ed25519", @["stable", "testing"]) check: result.isOk() result.get().rolloverType == "scheduled" result.get().overlapPeriod == initDuration(days = 30) test "Quantum Transition Planning": let logger = newSecurityEventLogger(testLogPath, testCasPath) var manager = newRevocationManager(testCrlPath, testCasPath, logger) let result = manager.planQuantumTransition("classical-key-123", "dilithium") check: result.isOk() result.get().rolloverType == "quantum-transition" result.get().overlapPeriod == initDuration(days = 60) result.get().affectedRepositories == @["all"] test "Offline Revocation Package": let logger = newSecurityEventLogger(testLogPath, testCasPath) let manager = newRevocationManager(testCrlPath, testCasPath, logger) let result = manager.createOfflineRevocationPackage(@["offline-key-1", "offline-key-2"]) check: result.isOk() fileExists(result.get()) suite "CLI Audit Commands": test "Audit Command Parsing - Log": let result = parseAuditCommand(@["log", "--follow", "--format", "json"]) check: result.isOk() result.get().command == AuditLog result.get().follow == true result.get().format == "json" test "Audit Command Parsing - Keys": let result = parseAuditCommand(@["keys", "--format", "table", "--verbose"]) check: result.isOk() result.get().command == AuditKeys result.get().format == "table" result.get().verbose == true test "Audit Command Parsing - Packages": let result = parseAuditCommand(@["packages", "--package", "htop", "--severity", "error"]) check: result.isOk() result.get().command == AuditPackages result.get().packageName.isSome() result.get().packageName.get() == "htop" result.get().severity.isSome() result.get().severity.get() == SeverityError test "Audit Command Parsing - Integrity": let result = parseAuditCommand(@["integrity", "--output", "/tmp/integrity_report.json"]) check: result.isOk() result.get().command == AuditIntegrity result.get().outputFile.isSome() result.get().outputFile.get() == "/tmp/integrity_report.json" test "Invalid Audit Command": let result = parseAuditCommand(@["invalid-command"]) check: result.isErr() result.errValue.contains("Unknown audit command") test "Missing Required Arguments": let result = parseAuditCommand(@["log", "--format"]) check: result.isErr() result.errValue.contains("--format requires a value") suite "Integration Tests": let testLogPath = "/tmp/nip_integration_security.log" let testCasPath = "/tmp/nip_integration_cas" let testCrlPath = "/tmp/nip_integration_crl" setup: if fileExists(testLogPath): removeFile(testLogPath) if dirExists(testCasPath): removeDir(testCasPath) if dirExists(testCrlPath): removeDir(testCrlPath) createDir(testCasPath) createDir(testCrlPath) teardown: if fileExists(testLogPath): removeFile(testLogPath) if dirExists(testCasPath): removeDir(testCasPath) if dirExists(testCrlPath): removeDir(testCrlPath) test "Complete Security Workflow": # Initialize components var logger = newSecurityEventLogger(testLogPath, testCasPath) var manager = newRevocationManager(testCrlPath, testCasPath, logger) # Set up policies let policies = getDefaultPolicies() for algorithm, policy in policies: manager.setRolloverPolicy(algorithm, policy) # Simulate security events logger.logSignatureVerification("package-1", "key-123", true) logger.logSignatureVerification("package-2", "key-456", false, "Invalid signature") # Perform emergency revocation let revocationResult = manager.emergencyRevocation("key-456", "Compromised key", @["package-2"]) check revocationResult.isOk() # Schedule rollover let rolloverResult = manager.scheduleKeyRollover("key-123", "ed25519", @["stable"]) check rolloverResult.isOk() # Verify log integrity let integrityResult = logger.verifyLogIntegrity() check integrityResult.valid # Check that all events were logged check: logger.eventCounter >= 4 # At least 4 events logged fileExists(testLogPath) fileExists(testCrlPath / "revocation_list.nexus") test "CLI Integration": # Set up test environment putEnv("NIP_SECURITY_LOG", testLogPath) putEnv("NIP_CAS_STORE", testCasPath) putEnv("NIP_CRL_PATH", testCrlPath) # Initialize and log some events var logger = newSecurityEventLogger(testLogPath, testCasPath) logger.logSignatureVerification("test-package", "test-key", true) logger.logTrustViolation("bad-package", "Untrusted source", "bad-key") # Test CLI commands let logResult = executeAuditCommand(@["log", "--format", "json"]) check logResult.isOk() let integrityResult = executeAuditCommand(@["integrity", "--format", "table"]) check integrityResult.isOk() when isMainModule: # Run the tests echo "Running Security Event Logging System Tests..." echo "=" .repeat(50)