444 lines
13 KiB
Nim
444 lines
13 KiB
Nim
## 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) |