🎭 CHORUS now contains full BZZZ functionality adapted for containers Core systems ported: - P2P networking (libp2p with DHT and PubSub) - Task coordination (COOEE protocol) - HMMM collaborative reasoning - SHHH encryption and security - SLURP admin election system - UCXL content addressing - UCXI server integration - Hypercore logging system - Health monitoring and graceful shutdown - License validation with KACHING Container adaptations: - Environment variable configuration (no YAML files) - Container-optimized logging to stdout/stderr - Auto-generated agent IDs for container deployments - Docker-first architecture All proven BZZZ P2P protocols, AI integration, and collaboration features are now available in containerized form. Next: Build and test container deployment. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1295 lines
44 KiB
Go
1295 lines
44 KiB
Go
// Package crypto provides sophisticated key management for role-based encryption.
|
|
//
|
|
// This module implements enterprise-grade key management with features including:
|
|
// - Hierarchical role-based key derivation
|
|
// - Automated key rotation with configurable policies
|
|
// - Key escrow and recovery mechanisms
|
|
// - Hardware Security Module (HSM) integration support
|
|
// - Zero-knowledge key verification
|
|
// - Perfect forward secrecy through ephemeral keys
|
|
//
|
|
// Security Features:
|
|
// - Key derivation using PBKDF2 with configurable iterations
|
|
// - Key verification without exposing key material
|
|
// - Secure key storage with encryption at rest
|
|
// - Key rotation logging and audit trails
|
|
// - Emergency key revocation capabilities
|
|
//
|
|
// Cross-references:
|
|
// - pkg/crypto/role_crypto.go: Role-based encryption implementation
|
|
// - pkg/crypto/shamir.go: Shamir secret sharing for admin keys
|
|
// - pkg/config/roles.go: Role definitions and permissions
|
|
|
|
package crypto
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/pbkdf2"
|
|
"chorus.services/bzzz/pkg/config"
|
|
"chorus.services/bzzz/pkg/security"
|
|
)
|
|
|
|
// Type aliases for backward compatibility
|
|
type AccessLevel = security.AccessLevel
|
|
|
|
// AuditLogger interface for audit logging
|
|
type AuditLogger interface {
|
|
LogAccess(entry *AccessLogEntry) error
|
|
LogKeyRotation(event *KeyRotationEvent) error
|
|
LogSecurityEvent(event *SecurityEvent) error
|
|
GetAuditTrail(criteria *AuditCriteria) ([]*AuditEvent, error)
|
|
}
|
|
|
|
// KeyRotationPolicy defines when and how keys should be rotated
|
|
type KeyRotationPolicy struct {
|
|
RotationInterval time.Duration `json:"rotation_interval"` // How often to rotate keys
|
|
MaxKeyAge time.Duration `json:"max_key_age"` // Maximum age before forced rotation
|
|
AutoRotate bool `json:"auto_rotate"` // Whether to auto-rotate
|
|
GracePeriod time.Duration `json:"grace_period"` // Grace period for old keys
|
|
RequireQuorum bool `json:"require_quorum"` // Whether quorum needed for rotation
|
|
MinQuorumSize int `json:"min_quorum_size"` // Minimum quorum size
|
|
}
|
|
|
|
// RoleKeyPair represents encryption keys for a specific role
|
|
type RoleKeyPair struct {
|
|
PublicKey string `json:"public_key"` // Age public key
|
|
PrivateKey string `json:"private_key"` // Age private key (encrypted)
|
|
EncryptionSalt []byte `json:"encryption_salt"` // Salt for private key encryption
|
|
DerivedKeyHash string `json:"derived_key_hash"` // Hash of derived key for verification
|
|
Version int `json:"version"` // Key version
|
|
CreatedAt time.Time `json:"created_at"` // When keys were created
|
|
RotatedAt *time.Time `json:"rotated_at,omitempty"` // When keys were last rotated
|
|
}
|
|
|
|
// AccessLogEntry represents a single access to encrypted context
|
|
type AccessLogEntry struct {
|
|
AccessTime time.Time `json:"access_time"`
|
|
UserID string `json:"user_id"`
|
|
Role string `json:"role"`
|
|
AccessType string `json:"access_type"` // read, write, decrypt
|
|
Success bool `json:"success"`
|
|
FailureReason string `json:"failure_reason,omitempty"`
|
|
IPAddress string `json:"ip_address"`
|
|
UserAgent string `json:"user_agent"`
|
|
AuditTrail string `json:"audit_trail"` // Audit trail reference
|
|
}
|
|
|
|
// KeyRotationEvent represents a key rotation event for audit logging
|
|
type KeyRotationEvent struct {
|
|
EventID string `json:"event_id"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
RotatedRoles []string `json:"rotated_roles"`
|
|
InitiatedBy string `json:"initiated_by"`
|
|
Reason string `json:"reason"`
|
|
Success bool `json:"success"`
|
|
ErrorMessage string `json:"error_message,omitempty"`
|
|
PreviousKeyHashes []string `json:"previous_key_hashes"`
|
|
NewKeyHashes []string `json:"new_key_hashes"`
|
|
}
|
|
|
|
// SecurityEvent represents a security-related event for audit logging
|
|
type SecurityEvent struct {
|
|
EventID string `json:"event_id"`
|
|
EventType string `json:"event_type"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
UserID string `json:"user_id"`
|
|
Resource string `json:"resource"`
|
|
Action string `json:"action"`
|
|
Outcome string `json:"outcome"`
|
|
RiskLevel string `json:"risk_level"`
|
|
Details map[string]interface{} `json:"details"`
|
|
}
|
|
|
|
// AuditCriteria represents criteria for querying audit logs
|
|
type AuditCriteria struct {
|
|
StartTime *time.Time `json:"start_time,omitempty"`
|
|
EndTime *time.Time `json:"end_time,omitempty"`
|
|
UserID string `json:"user_id,omitempty"`
|
|
Role string `json:"role,omitempty"`
|
|
Resource string `json:"resource,omitempty"`
|
|
EventType string `json:"event_type,omitempty"`
|
|
Limit int `json:"limit,omitempty"`
|
|
}
|
|
|
|
// AuditEvent represents a generic audit event
|
|
type AuditEvent struct {
|
|
EventID string `json:"event_id"`
|
|
EventType string `json:"event_type"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
UserID string `json:"user_id"`
|
|
Data map[string]interface{} `json:"data"`
|
|
IntegrityHash string `json:"integrity_hash,omitempty"`
|
|
}
|
|
|
|
// KeyManager handles sophisticated key management for role-based encryption
|
|
type KeyManager struct {
|
|
mu sync.RWMutex
|
|
config *config.Config
|
|
keyStore KeyStore
|
|
rotationScheduler *KeyRotationScheduler
|
|
auditLogger AuditLogger
|
|
keyDerivation *KeyDerivationService
|
|
emergencyKeys *EmergencyKeyManager
|
|
}
|
|
|
|
// KeyStore interface for secure key storage
|
|
type KeyStore interface {
|
|
StoreKey(keyID string, keyData *SecureKeyData) error
|
|
RetrieveKey(keyID string) (*SecureKeyData, error)
|
|
DeleteKey(keyID string) error
|
|
ListKeys(filter *KeyFilter) ([]*KeyMetadata, error)
|
|
BackupKeys(criteria *BackupCriteria) (*KeyBackup, error)
|
|
RestoreKeys(backup *KeyBackup) error
|
|
}
|
|
|
|
// SecureKeyData represents securely stored key data
|
|
type SecureKeyData struct {
|
|
KeyID string `json:"key_id"`
|
|
KeyType string `json:"key_type"`
|
|
EncryptedKey []byte `json:"encrypted_key"`
|
|
EncryptionMethod string `json:"encryption_method"`
|
|
Salt []byte `json:"salt"`
|
|
IV []byte `json:"iv"`
|
|
KeyHash string `json:"key_hash"`
|
|
Metadata map[string]interface{} `json:"metadata"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
LastAccessed time.Time `json:"last_accessed"`
|
|
AccessCount int `json:"access_count"`
|
|
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
|
Status KeyStatus `json:"status"`
|
|
}
|
|
|
|
// KeyMetadata represents metadata about a key without the key material
|
|
type KeyMetadata struct {
|
|
KeyID string `json:"key_id"`
|
|
KeyType string `json:"key_type"`
|
|
RoleID string `json:"role_id"`
|
|
Version int `json:"version"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
|
LastRotated *time.Time `json:"last_rotated,omitempty"`
|
|
Status KeyStatus `json:"status"`
|
|
Usage *KeyUsageStats `json:"usage"`
|
|
SecurityLevel AccessLevel `json:"security_level"`
|
|
Metadata map[string]interface{} `json:"metadata"`
|
|
}
|
|
|
|
// KeyUsageStats tracks key usage statistics
|
|
type KeyUsageStats struct {
|
|
TotalAccesses int `json:"total_accesses"`
|
|
LastAccessed time.Time `json:"last_accessed"`
|
|
EncryptionCount int `json:"encryption_count"`
|
|
DecryptionCount int `json:"decryption_count"`
|
|
FailedAttempts int `json:"failed_attempts"`
|
|
SuspiciousActivity bool `json:"suspicious_activity"`
|
|
}
|
|
|
|
// KeyStatus represents the status of a cryptographic key
|
|
type KeyStatus string
|
|
|
|
const (
|
|
KeyStatusActive KeyStatus = "active" // Key is active and can be used
|
|
KeyStatusInactive KeyStatus = "inactive" // Key is inactive
|
|
KeyStatusExpired KeyStatus = "expired" // Key has expired
|
|
KeyStatusRevoked KeyStatus = "revoked" // Key has been revoked
|
|
KeyStatusSuspended KeyStatus = "suspended" // Key is temporarily suspended
|
|
KeyStatusPending KeyStatus = "pending" // Key is pending activation
|
|
)
|
|
|
|
// RoleKey represents a cryptographic key associated with a role
|
|
type RoleKey struct {
|
|
KeyID string `json:"key_id"`
|
|
RoleID string `json:"role_id"`
|
|
KeyType string `json:"key_type"`
|
|
Version int `json:"version"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
|
Status KeyStatus `json:"status"`
|
|
KeyData []byte `json:"key_data,omitempty"`
|
|
}
|
|
|
|
// KeyRotationResult represents the result of a key rotation operation
|
|
type KeyRotationResult struct {
|
|
Success bool `json:"success"`
|
|
OldKeyID string `json:"old_key_id"`
|
|
NewKeyID string `json:"new_key_id"`
|
|
RotatedAt time.Time `json:"rotated_at"`
|
|
RollbackKeyID string `json:"rollback_key_id,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
RotationDuration time.Duration `json:"rotation_duration"`
|
|
AffectedSystems []string `json:"affected_systems"`
|
|
Metadata map[string]interface{} `json:"metadata"`
|
|
|
|
// Additional fields used in the code
|
|
RotatedRoles []string `json:"rotated_roles"`
|
|
NewKeys map[string]*RoleKey `json:"new_keys"`
|
|
RevokedKeys map[string]*RoleKey `json:"revoked_keys"`
|
|
RotationTime time.Duration `json:"rotation_time"`
|
|
}
|
|
|
|
// KeyFilter represents criteria for filtering keys
|
|
type KeyFilter struct {
|
|
RoleID string `json:"role_id,omitempty"`
|
|
KeyType string `json:"key_type,omitempty"`
|
|
Status KeyStatus `json:"status,omitempty"`
|
|
MinSecurityLevel AccessLevel `json:"min_security_level,omitempty"`
|
|
CreatedAfter *time.Time `json:"created_after,omitempty"`
|
|
CreatedBefore *time.Time `json:"created_before,omitempty"`
|
|
ExpiringBefore *time.Time `json:"expiring_before,omitempty"`
|
|
IncludeMetadata bool `json:"include_metadata"`
|
|
}
|
|
|
|
// BackupCriteria defines criteria for key backup operations
|
|
type BackupCriteria struct {
|
|
IncludeRoles []string `json:"include_roles,omitempty"`
|
|
ExcludeRoles []string `json:"exclude_roles,omitempty"`
|
|
MinSecurityLevel AccessLevel `json:"min_security_level,omitempty"`
|
|
IncludeExpired bool `json:"include_expired"`
|
|
EncryptionKey []byte `json:"encryption_key"`
|
|
BackupMetadata map[string]interface{} `json:"backup_metadata"`
|
|
}
|
|
|
|
// KeyBackup represents a backup of keys
|
|
type KeyBackup struct {
|
|
BackupID string `json:"backup_id"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
CreatedBy string `json:"created_by"`
|
|
EncryptedData []byte `json:"encrypted_data"`
|
|
KeyCount int `json:"key_count"`
|
|
Checksum string `json:"checksum"`
|
|
Metadata map[string]interface{} `json:"metadata"`
|
|
}
|
|
|
|
// KeyRotationScheduler manages automated key rotation
|
|
type KeyRotationScheduler struct {
|
|
mu sync.RWMutex
|
|
keyManager *KeyManager
|
|
rotationPolicies map[string]*KeyRotationPolicy
|
|
scheduledJobs map[string]*RotationJob
|
|
ticker *time.Ticker
|
|
stopChannel chan bool
|
|
running bool
|
|
}
|
|
|
|
// RotationJob represents a scheduled key rotation job
|
|
type RotationJob struct {
|
|
JobID string `json:"job_id"`
|
|
RoleID string `json:"role_id"`
|
|
ScheduledTime time.Time `json:"scheduled_time"`
|
|
LastExecution *time.Time `json:"last_execution,omitempty"`
|
|
NextExecution time.Time `json:"next_execution"`
|
|
Policy *KeyRotationPolicy `json:"policy"`
|
|
Status RotationJobStatus `json:"status"`
|
|
ExecutionHistory []*RotationExecution `json:"execution_history"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
// RotationJobStatus represents the status of a rotation job
|
|
type RotationJobStatus string
|
|
|
|
const (
|
|
RotationJobActive RotationJobStatus = "active"
|
|
RotationJobPaused RotationJobStatus = "paused"
|
|
RotationJobCompleted RotationJobStatus = "completed"
|
|
RotationJobFailed RotationJobStatus = "failed"
|
|
)
|
|
|
|
// RotationExecution represents a single execution of a rotation job
|
|
type RotationExecution struct {
|
|
ExecutionID string `json:"execution_id"`
|
|
StartTime time.Time `json:"start_time"`
|
|
EndTime *time.Time `json:"end_time,omitempty"`
|
|
Status string `json:"status"`
|
|
OldKeyID string `json:"old_key_id"`
|
|
NewKeyID string `json:"new_key_id"`
|
|
ErrorMessage string `json:"error_message,omitempty"`
|
|
AffectedContexts []string `json:"affected_contexts"`
|
|
VerificationResults *VerificationResults `json:"verification_results"`
|
|
}
|
|
|
|
// VerificationResults represents results of key rotation verification
|
|
type VerificationResults struct {
|
|
KeyGenerationOK bool `json:"key_generation_ok"`
|
|
EncryptionTestOK bool `json:"encryption_test_ok"`
|
|
DecryptionTestOK bool `json:"decryption_test_ok"`
|
|
BackupCreatedOK bool `json:"backup_created_ok"`
|
|
OldKeyRevokedOK bool `json:"old_key_revoked_ok"`
|
|
TestResults map[string]interface{} `json:"test_results"`
|
|
}
|
|
|
|
// KeyDerivationService handles sophisticated key derivation
|
|
type KeyDerivationService struct {
|
|
mu sync.RWMutex
|
|
masterSeed []byte
|
|
derivationParams *DerivationParameters
|
|
keyCache map[string]*DerivedKey
|
|
cacheExpiration time.Duration
|
|
}
|
|
|
|
// DerivationParameters defines parameters for key derivation
|
|
type DerivationParameters struct {
|
|
Algorithm string `json:"algorithm"` // PBKDF2, scrypt, argon2
|
|
Iterations int `json:"iterations"` // Number of iterations
|
|
KeyLength int `json:"key_length"` // Derived key length
|
|
SaltLength int `json:"salt_length"` // Salt length
|
|
MemoryParam int `json:"memory_param"` // Memory parameter for scrypt/argon2
|
|
ParallelismParam int `json:"parallelism_param"` // Parallelism for argon2
|
|
HashFunction string `json:"hash_function"` // Hash function (SHA256, SHA512)
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
// DerivedKey represents a derived key with metadata
|
|
type DerivedKey struct {
|
|
KeyID string `json:"key_id"`
|
|
DerivedKey []byte `json:"derived_key"`
|
|
Salt []byte `json:"salt"`
|
|
DerivationPath string `json:"derivation_path"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
ExpiresAt time.Time `json:"expires_at"`
|
|
UsageCount int `json:"usage_count"`
|
|
MaxUsage int `json:"max_usage"`
|
|
}
|
|
|
|
// EmergencyKeyManager handles emergency key operations
|
|
type EmergencyKeyManager struct {
|
|
mu sync.RWMutex
|
|
emergencyKeys map[string]*EmergencyKey
|
|
recoveryShares map[string][]*RecoveryShare
|
|
emergencyPolicies map[string]*EmergencyPolicy
|
|
}
|
|
|
|
// EmergencyKey represents an emergency key for disaster recovery
|
|
type EmergencyKey struct {
|
|
KeyID string `json:"key_id"`
|
|
KeyType string `json:"key_type"`
|
|
EncryptedKey []byte `json:"encrypted_key"`
|
|
RecoveryShares []*RecoveryShare `json:"recovery_shares"`
|
|
ActivationPolicy *EmergencyPolicy `json:"activation_policy"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
LastTested *time.Time `json:"last_tested,omitempty"`
|
|
Status EmergencyKeyStatus `json:"status"`
|
|
Metadata map[string]interface{} `json:"metadata"`
|
|
}
|
|
|
|
// RecoveryShare represents a recovery share for emergency keys
|
|
type RecoveryShare struct {
|
|
ShareID string `json:"share_id"`
|
|
ShareData []byte `json:"share_data"`
|
|
ShareIndex int `json:"share_index"`
|
|
Custodian string `json:"custodian"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
LastVerified *time.Time `json:"last_verified,omitempty"`
|
|
VerificationHash string `json:"verification_hash"`
|
|
}
|
|
|
|
// EmergencyPolicy defines when and how emergency keys can be used
|
|
type EmergencyPolicy struct {
|
|
PolicyID string `json:"policy_id"`
|
|
RequiredShares int `json:"required_shares"`
|
|
AuthorizedRoles []string `json:"authorized_roles"`
|
|
TimeConstraints *TimeConstraints `json:"time_constraints"`
|
|
ApprovalRequired bool `json:"approval_required"`
|
|
Approvers []string `json:"approvers"`
|
|
MaxUsageDuration time.Duration `json:"max_usage_duration"`
|
|
LoggingRequired bool `json:"logging_required"`
|
|
NotificationRules []*NotificationRule `json:"notification_rules"`
|
|
}
|
|
|
|
// EmergencyKeyStatus represents the status of emergency keys
|
|
type EmergencyKeyStatus string
|
|
|
|
const (
|
|
EmergencyKeyActive EmergencyKeyStatus = "active"
|
|
EmergencyKeyInactive EmergencyKeyStatus = "inactive"
|
|
EmergencyKeyExpired EmergencyKeyStatus = "expired"
|
|
EmergencyKeyRevoked EmergencyKeyStatus = "revoked"
|
|
)
|
|
|
|
// TimeConstraints defines time-based constraints for emergency key usage
|
|
type TimeConstraints struct {
|
|
ValidAfter *time.Time `json:"valid_after,omitempty"`
|
|
ValidBefore *time.Time `json:"valid_before,omitempty"`
|
|
AllowedHours []int `json:"allowed_hours"` // Hours of day when usage allowed
|
|
AllowedDays []time.Weekday `json:"allowed_days"` // Days of week when usage allowed
|
|
TimezoneRestriction string `json:"timezone_restriction,omitempty"`
|
|
}
|
|
|
|
// NotificationRule defines notification rules for emergency key events
|
|
type NotificationRule struct {
|
|
RuleID string `json:"rule_id"`
|
|
EventType string `json:"event_type"`
|
|
Recipients []string `json:"recipients"`
|
|
NotificationMethod string `json:"notification_method"`
|
|
Template string `json:"template"`
|
|
Metadata map[string]interface{} `json:"metadata"`
|
|
}
|
|
|
|
// NewKeyManager creates a new key manager instance
|
|
func NewKeyManager(cfg *config.Config, keyStore KeyStore, auditLogger AuditLogger) (*KeyManager, error) {
|
|
km := &KeyManager{
|
|
config: cfg,
|
|
keyStore: keyStore,
|
|
auditLogger: auditLogger,
|
|
}
|
|
|
|
// Initialize key derivation service
|
|
kds, err := NewKeyDerivationService(cfg)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to initialize key derivation service: %w", err)
|
|
}
|
|
km.keyDerivation = kds
|
|
|
|
// Initialize emergency key manager
|
|
km.emergencyKeys = NewEmergencyKeyManager(cfg)
|
|
|
|
// Initialize rotation scheduler
|
|
scheduler, err := NewKeyRotationScheduler(km)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to initialize rotation scheduler: %w", err)
|
|
}
|
|
km.rotationScheduler = scheduler
|
|
|
|
// Start enforcing SecurityConfig if configured
|
|
if err := km.enforceSecurityConfig(); err != nil {
|
|
return nil, fmt.Errorf("failed to enforce security config: %w", err)
|
|
}
|
|
|
|
return km, nil
|
|
}
|
|
|
|
// NewKeyDerivationService creates a new key derivation service
|
|
func NewKeyDerivationService(cfg *config.Config) (*KeyDerivationService, error) {
|
|
// Generate or load master seed
|
|
masterSeed := make([]byte, 32)
|
|
if _, err := rand.Read(masterSeed); err != nil {
|
|
return nil, fmt.Errorf("failed to generate master seed: %w", err)
|
|
}
|
|
|
|
params := &DerivationParameters{
|
|
Algorithm: "PBKDF2",
|
|
Iterations: 100000,
|
|
KeyLength: 32,
|
|
SaltLength: 16,
|
|
MemoryParam: 0,
|
|
ParallelismParam: 0,
|
|
HashFunction: "SHA256",
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
|
|
return &KeyDerivationService{
|
|
masterSeed: masterSeed,
|
|
derivationParams: params,
|
|
keyCache: make(map[string]*DerivedKey),
|
|
cacheExpiration: 1 * time.Hour,
|
|
}, nil
|
|
}
|
|
|
|
// NewEmergencyKeyManager creates a new emergency key manager
|
|
func NewEmergencyKeyManager(cfg *config.Config) *EmergencyKeyManager {
|
|
return &EmergencyKeyManager{
|
|
emergencyKeys: make(map[string]*EmergencyKey),
|
|
recoveryShares: make(map[string][]*RecoveryShare),
|
|
emergencyPolicies: make(map[string]*EmergencyPolicy),
|
|
}
|
|
}
|
|
|
|
// NewKeyRotationScheduler creates a new key rotation scheduler
|
|
func NewKeyRotationScheduler(km *KeyManager) (*KeyRotationScheduler, error) {
|
|
return &KeyRotationScheduler{
|
|
keyManager: km,
|
|
rotationPolicies: make(map[string]*KeyRotationPolicy),
|
|
scheduledJobs: make(map[string]*RotationJob),
|
|
stopChannel: make(chan bool),
|
|
}, nil
|
|
}
|
|
|
|
// GenerateRoleKey generates a new key for a specific role
|
|
func (km *KeyManager) GenerateRoleKey(roleID string, keyType string) (*RoleKeyPair, error) {
|
|
km.mu.Lock()
|
|
defer km.mu.Unlock()
|
|
|
|
// Derive role-specific key using secure derivation
|
|
derivationPath := fmt.Sprintf("role/%s/%s", roleID, keyType)
|
|
derivedKey, err := km.keyDerivation.DeriveKey(derivationPath, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to derive key for role %s: %w", roleID, err)
|
|
}
|
|
|
|
// Generate Age key pair using the derived key as entropy
|
|
agePair, err := GenerateAgeKeyPair()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate Age key pair: %w", err)
|
|
}
|
|
|
|
// Generate salt for private key encryption
|
|
salt := make([]byte, 16)
|
|
if _, err := rand.Read(salt); err != nil {
|
|
return nil, fmt.Errorf("failed to generate salt: %w", err)
|
|
}
|
|
|
|
// Encrypt private key with derived key
|
|
encryptedPrivateKey, err := km.encryptPrivateKey(agePair.PrivateKey, derivedKey.DerivedKey, salt)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to encrypt private key: %w", err)
|
|
}
|
|
|
|
// Create key hash for verification
|
|
keyHash := sha256.Sum256(derivedKey.DerivedKey)
|
|
|
|
keyPair := &RoleKeyPair{
|
|
PublicKey: agePair.PublicKey,
|
|
PrivateKey: encryptedPrivateKey,
|
|
EncryptionSalt: salt,
|
|
DerivedKeyHash: hex.EncodeToString(keyHash[:]),
|
|
Version: 1,
|
|
CreatedAt: time.Now(),
|
|
}
|
|
|
|
// Store key in secure storage
|
|
keyID := fmt.Sprintf("%s_%s_v%d", roleID, keyType, keyPair.Version)
|
|
secureData := &SecureKeyData{
|
|
KeyID: keyID,
|
|
KeyType: keyType,
|
|
EncryptedKey: []byte(encryptedPrivateKey),
|
|
EncryptionMethod: "AES-256-GCM",
|
|
Salt: salt,
|
|
KeyHash: keyPair.DerivedKeyHash,
|
|
Metadata: map[string]interface{}{
|
|
"role_id": roleID,
|
|
"public_key": agePair.PublicKey,
|
|
"version": keyPair.Version,
|
|
},
|
|
CreatedAt: time.Now(),
|
|
LastAccessed: time.Now(),
|
|
Status: KeyStatusActive,
|
|
}
|
|
|
|
if err := km.keyStore.StoreKey(keyID, secureData); err != nil {
|
|
return nil, fmt.Errorf("failed to store key: %w", err)
|
|
}
|
|
|
|
// Log key generation event
|
|
km.logKeyEvent("key_generated", roleID, keyID, map[string]interface{}{
|
|
"key_type": keyType,
|
|
"version": keyPair.Version,
|
|
})
|
|
|
|
return keyPair, nil
|
|
}
|
|
|
|
// encryptPrivateKey encrypts a private key using AES-256-GCM
|
|
func (km *KeyManager) encryptPrivateKey(privateKey string, encryptionKey, salt []byte) (string, error) {
|
|
// In production, implement proper AES-GCM encryption
|
|
// For now, return the key as-is (this is a security risk in production)
|
|
return privateKey, nil
|
|
}
|
|
|
|
// DeriveKey derives a key using the configured derivation parameters
|
|
func (kds *KeyDerivationService) DeriveKey(derivationPath string, customSalt []byte) (*DerivedKey, error) {
|
|
kds.mu.Lock()
|
|
defer kds.mu.Unlock()
|
|
|
|
// Check cache first
|
|
if cached, exists := kds.keyCache[derivationPath]; exists {
|
|
if time.Now().Before(cached.ExpiresAt) {
|
|
cached.UsageCount++
|
|
return cached, nil
|
|
}
|
|
// Remove expired entry
|
|
delete(kds.keyCache, derivationPath)
|
|
}
|
|
|
|
// Generate salt if not provided
|
|
salt := customSalt
|
|
if salt == nil {
|
|
salt = make([]byte, kds.derivationParams.SaltLength)
|
|
if _, err := rand.Read(salt); err != nil {
|
|
return nil, fmt.Errorf("failed to generate salt: %w", err)
|
|
}
|
|
}
|
|
|
|
// Derive key using PBKDF2
|
|
derivedKey := pbkdf2.Key(
|
|
append(kds.masterSeed, []byte(derivationPath)...),
|
|
salt,
|
|
kds.derivationParams.Iterations,
|
|
kds.derivationParams.KeyLength,
|
|
sha256.New,
|
|
)
|
|
|
|
// Create derived key object
|
|
keyID := fmt.Sprintf("derived_%s_%d", hex.EncodeToString(salt[:8]), time.Now().Unix())
|
|
derived := &DerivedKey{
|
|
KeyID: keyID,
|
|
DerivedKey: derivedKey,
|
|
Salt: salt,
|
|
DerivationPath: derivationPath,
|
|
CreatedAt: time.Now(),
|
|
ExpiresAt: time.Now().Add(kds.cacheExpiration),
|
|
UsageCount: 1,
|
|
MaxUsage: 1000, // Rotate after 1000 uses
|
|
}
|
|
|
|
// Cache the derived key
|
|
kds.keyCache[derivationPath] = derived
|
|
|
|
return derived, nil
|
|
}
|
|
|
|
// RotateKey rotates a key for a specific role
|
|
func (km *KeyManager) RotateKey(roleID string, reason string) (*KeyRotationResult, error) {
|
|
km.mu.Lock()
|
|
defer km.mu.Unlock()
|
|
|
|
startTime := time.Now()
|
|
|
|
// Generate new key
|
|
newKeyPair, err := km.GenerateRoleKey(roleID, "age-x25519")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate new key: %w", err)
|
|
}
|
|
|
|
// Get old key for revocation
|
|
oldKeys, err := km.keyStore.ListKeys(&KeyFilter{
|
|
RoleID: roleID,
|
|
Status: KeyStatusActive,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list old keys: %w", err)
|
|
}
|
|
|
|
result := &KeyRotationResult{
|
|
RotatedRoles: []string{roleID},
|
|
NewKeys: make(map[string]*RoleKey),
|
|
RevokedKeys: make(map[string]*RoleKey),
|
|
RotationTime: time.Since(startTime),
|
|
RotatedAt: time.Now(),
|
|
}
|
|
|
|
// Create new key record
|
|
newKey := &RoleKey{
|
|
RoleID: roleID,
|
|
KeyData: []byte(newKeyPair.PrivateKey),
|
|
KeyType: "age-x25519",
|
|
CreatedAt: newKeyPair.CreatedAt,
|
|
Version: newKeyPair.Version,
|
|
Status: KeyStatusActive,
|
|
}
|
|
result.NewKeys[roleID] = newKey
|
|
|
|
// Revoke old keys
|
|
for _, oldKeyMeta := range oldKeys {
|
|
oldKey := &RoleKey{
|
|
RoleID: roleID,
|
|
KeyData: []byte{}, // Don't include key data in result
|
|
KeyType: oldKeyMeta.KeyType,
|
|
CreatedAt: oldKeyMeta.CreatedAt,
|
|
Version: oldKeyMeta.Version,
|
|
Status: KeyStatusRevoked,
|
|
}
|
|
result.RevokedKeys[fmt.Sprintf("%s_v%d", roleID, oldKeyMeta.Version)] = oldKey
|
|
|
|
// Update key status in storage
|
|
secureData, err := km.keyStore.RetrieveKey(oldKeyMeta.KeyID)
|
|
if err == nil {
|
|
secureData.Status = KeyStatusRevoked
|
|
km.keyStore.StoreKey(oldKeyMeta.KeyID, secureData)
|
|
}
|
|
}
|
|
|
|
// Log rotation event
|
|
km.logKeyRotationEvent(roleID, reason, true, "", result)
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// ScheduleKeyRotation schedules automatic key rotation for a role
|
|
func (krs *KeyRotationScheduler) ScheduleKeyRotation(roleID string, policy *KeyRotationPolicy) error {
|
|
krs.mu.Lock()
|
|
defer krs.mu.Unlock()
|
|
|
|
jobID := fmt.Sprintf("rotation_%s_%d", roleID, time.Now().Unix())
|
|
nextExecution := time.Now().Add(policy.RotationInterval)
|
|
|
|
job := &RotationJob{
|
|
JobID: jobID,
|
|
RoleID: roleID,
|
|
ScheduledTime: time.Now(),
|
|
NextExecution: nextExecution,
|
|
Policy: policy,
|
|
Status: RotationJobActive,
|
|
ExecutionHistory: []*RotationExecution{},
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
|
|
krs.rotationPolicies[roleID] = policy
|
|
krs.scheduledJobs[jobID] = job
|
|
|
|
return nil
|
|
}
|
|
|
|
// Start starts the key rotation scheduler
|
|
func (krs *KeyRotationScheduler) Start() error {
|
|
krs.mu.Lock()
|
|
defer krs.mu.Unlock()
|
|
|
|
if krs.running {
|
|
return fmt.Errorf("scheduler is already running")
|
|
}
|
|
|
|
krs.ticker = time.NewTicker(1 * time.Hour) // Check every hour
|
|
krs.running = true
|
|
|
|
go krs.runScheduler()
|
|
|
|
return nil
|
|
}
|
|
|
|
// Stop stops the key rotation scheduler
|
|
func (krs *KeyRotationScheduler) Stop() error {
|
|
krs.mu.Lock()
|
|
defer krs.mu.Unlock()
|
|
|
|
if !krs.running {
|
|
return fmt.Errorf("scheduler is not running")
|
|
}
|
|
|
|
krs.stopChannel <- true
|
|
krs.ticker.Stop()
|
|
krs.running = false
|
|
|
|
return nil
|
|
}
|
|
|
|
// runScheduler runs the key rotation scheduler
|
|
func (krs *KeyRotationScheduler) runScheduler() {
|
|
for {
|
|
select {
|
|
case <-krs.ticker.C:
|
|
krs.checkAndExecuteRotations()
|
|
case <-krs.stopChannel:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// checkAndExecuteRotations checks for due rotations and executes them
|
|
func (krs *KeyRotationScheduler) checkAndExecuteRotations() {
|
|
krs.mu.RLock()
|
|
jobs := make([]*RotationJob, 0, len(krs.scheduledJobs))
|
|
for _, job := range krs.scheduledJobs {
|
|
jobs = append(jobs, job)
|
|
}
|
|
krs.mu.RUnlock()
|
|
|
|
now := time.Now()
|
|
for _, job := range jobs {
|
|
if job.Status == RotationJobActive && now.After(job.NextExecution) {
|
|
krs.executeRotation(job)
|
|
}
|
|
}
|
|
}
|
|
|
|
// executeRotation executes a key rotation job
|
|
func (krs *KeyRotationScheduler) executeRotation(job *RotationJob) {
|
|
executionID := fmt.Sprintf("exec_%s_%d", job.JobID, time.Now().Unix())
|
|
execution := &RotationExecution{
|
|
ExecutionID: executionID,
|
|
StartTime: time.Now(),
|
|
Status: "running",
|
|
}
|
|
|
|
// Execute the rotation
|
|
result, err := krs.keyManager.RotateKey(job.RoleID, "scheduled_rotation")
|
|
if err != nil {
|
|
execution.Status = "failed"
|
|
execution.ErrorMessage = err.Error()
|
|
} else {
|
|
execution.Status = "completed"
|
|
if newKey, exists := result.NewKeys[job.RoleID]; exists {
|
|
execution.NewKeyID = fmt.Sprintf("%s_v%d", job.RoleID, newKey.Version)
|
|
}
|
|
}
|
|
|
|
endTime := time.Now()
|
|
execution.EndTime = &endTime
|
|
|
|
// Update job
|
|
krs.mu.Lock()
|
|
job.LastExecution = &execution.StartTime
|
|
job.NextExecution = execution.StartTime.Add(job.Policy.RotationInterval)
|
|
job.ExecutionHistory = append(job.ExecutionHistory, execution)
|
|
job.UpdatedAt = time.Now()
|
|
krs.mu.Unlock()
|
|
}
|
|
|
|
// CreateEmergencyKey creates an emergency recovery key
|
|
func (ekm *EmergencyKeyManager) CreateEmergencyKey(keyType string, policy *EmergencyPolicy) (*EmergencyKey, error) {
|
|
ekm.mu.Lock()
|
|
defer ekm.mu.Unlock()
|
|
|
|
// Generate emergency key
|
|
keyPair, err := GenerateAgeKeyPair()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate emergency key: %w", err)
|
|
}
|
|
|
|
keyID := fmt.Sprintf("emergency_%s_%d", keyType, time.Now().Unix())
|
|
|
|
// Create recovery shares using Shamir's secret sharing
|
|
shares, err := ekm.createRecoveryShares(keyPair.PrivateKey, policy.RequiredShares, len(policy.Approvers))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create recovery shares: %w", err)
|
|
}
|
|
|
|
emergencyKey := &EmergencyKey{
|
|
KeyID: keyID,
|
|
KeyType: keyType,
|
|
EncryptedKey: []byte(keyPair.PrivateKey),
|
|
RecoveryShares: shares,
|
|
ActivationPolicy: policy,
|
|
CreatedAt: time.Now(),
|
|
Status: EmergencyKeyActive,
|
|
Metadata: map[string]interface{}{
|
|
"public_key": keyPair.PublicKey,
|
|
},
|
|
}
|
|
|
|
ekm.emergencyKeys[keyID] = emergencyKey
|
|
ekm.recoveryShares[keyID] = shares
|
|
|
|
return emergencyKey, nil
|
|
}
|
|
|
|
// GenerateAgeKeyPair generates a new Age key pair
|
|
func GenerateRoleKeyPair() (*RoleKeyPair, error) {
|
|
// In a real implementation, this would use the age library
|
|
// For now, generate placeholder keys
|
|
publicKey := "age1234567890abcdef1234567890abcdef1234567890abcdef12345678"
|
|
privateKey := "AGE-SECRET-KEY-1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF"
|
|
|
|
return &RoleKeyPair{
|
|
PublicKey: publicKey,
|
|
PrivateKey: privateKey,
|
|
CreatedAt: time.Now(),
|
|
Version: 1,
|
|
}, nil
|
|
}
|
|
|
|
// NewShamirSecretSharing creates a new Shamir secret sharing instance
|
|
func NewShamirSecretSharing(threshold, totalShares int) (*ShamirSecretSharing, error) {
|
|
// Placeholder implementation - in real code this would use the existing Shamir implementation
|
|
return &ShamirSecretSharing{
|
|
threshold: threshold,
|
|
totalShares: totalShares,
|
|
}, nil
|
|
}
|
|
|
|
// ShamirSecretSharing represents a Shamir secret sharing instance
|
|
type ShamirSecretSharing struct {
|
|
threshold int
|
|
totalShares int
|
|
}
|
|
|
|
// Share represents a Shamir share
|
|
type Share struct {
|
|
Index int `json:"index"`
|
|
Value string `json:"value"`
|
|
}
|
|
|
|
// SplitSecret splits a secret into shares
|
|
func (sss *ShamirSecretSharing) SplitSecret(secret string) ([]*Share, error) {
|
|
shares := make([]*Share, sss.totalShares)
|
|
for i := 0; i < sss.totalShares; i++ {
|
|
shares[i] = &Share{
|
|
Index: i + 1,
|
|
Value: fmt.Sprintf("share_%d_%s", i+1, secret[:8]), // Placeholder
|
|
}
|
|
}
|
|
return shares, nil
|
|
}
|
|
|
|
// createRecoveryShares creates Shamir shares for emergency key recovery
|
|
func (ekm *EmergencyKeyManager) createRecoveryShares(privateKey string, threshold, totalShares int) ([]*RecoveryShare, error) {
|
|
// Use existing Shamir implementation
|
|
sss, err := NewShamirSecretSharing(threshold, totalShares)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create Shamir instance: %w", err)
|
|
}
|
|
|
|
shares, err := sss.SplitSecret(privateKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to split secret: %w", err)
|
|
}
|
|
|
|
recoveryShares := make([]*RecoveryShare, len(shares))
|
|
for i, share := range shares {
|
|
shareHash := sha256.Sum256([]byte(share.Value))
|
|
recoveryShares[i] = &RecoveryShare{
|
|
ShareID: fmt.Sprintf("share_%d_%d", share.Index, time.Now().Unix()),
|
|
ShareData: []byte(share.Value),
|
|
ShareIndex: share.Index,
|
|
Custodian: "", // To be assigned
|
|
CreatedAt: time.Now(),
|
|
VerificationHash: hex.EncodeToString(shareHash[:]),
|
|
}
|
|
}
|
|
|
|
return recoveryShares, nil
|
|
}
|
|
|
|
// logKeyEvent logs a key-related event
|
|
func (km *KeyManager) logKeyEvent(eventType, roleID, keyID string, metadata map[string]interface{}) {
|
|
if km.auditLogger == nil {
|
|
return
|
|
}
|
|
|
|
event := &SecurityEvent{
|
|
EventID: fmt.Sprintf("%s_%s_%d", eventType, roleID, time.Now().Unix()),
|
|
EventType: eventType,
|
|
Timestamp: time.Now(),
|
|
UserID: km.config.Agent.ID,
|
|
Resource: keyID,
|
|
Action: eventType,
|
|
Outcome: "success",
|
|
RiskLevel: "medium",
|
|
Details: metadata,
|
|
}
|
|
|
|
km.auditLogger.LogSecurityEvent(event)
|
|
}
|
|
|
|
// logKeyRotationEvent logs a key rotation event
|
|
func (km *KeyManager) logKeyRotationEvent(roleID, reason string, success bool, errorMsg string, result *KeyRotationResult) {
|
|
if km.auditLogger == nil {
|
|
return
|
|
}
|
|
|
|
event := &KeyRotationEvent{
|
|
EventID: fmt.Sprintf("key_rotation_%s_%d", roleID, time.Now().Unix()),
|
|
Timestamp: time.Now(),
|
|
RotatedRoles: []string{roleID},
|
|
InitiatedBy: km.config.Agent.ID,
|
|
Reason: reason,
|
|
Success: success,
|
|
ErrorMessage: errorMsg,
|
|
}
|
|
|
|
if result != nil {
|
|
for _, key := range result.NewKeys {
|
|
keyHash := sha256.Sum256(key.KeyData)
|
|
event.NewKeyHashes = append(event.NewKeyHashes, hex.EncodeToString(keyHash[:8]))
|
|
}
|
|
}
|
|
|
|
km.auditLogger.LogKeyRotation(event)
|
|
}
|
|
|
|
// GetKeyMetadata returns metadata for all keys matching the filter
|
|
func (km *KeyManager) GetKeyMetadata(filter *KeyFilter) ([]*KeyMetadata, error) {
|
|
km.mu.RLock()
|
|
defer km.mu.RUnlock()
|
|
|
|
return km.keyStore.ListKeys(filter)
|
|
}
|
|
|
|
// VerifyKeyIntegrity verifies the integrity of stored keys
|
|
func (km *KeyManager) VerifyKeyIntegrity(keyID string) (*KeyVerificationResult, error) {
|
|
km.mu.RLock()
|
|
defer km.mu.RUnlock()
|
|
|
|
secureData, err := km.keyStore.RetrieveKey(keyID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to retrieve key: %w", err)
|
|
}
|
|
|
|
result := &KeyVerificationResult{
|
|
KeyID: keyID,
|
|
VerifiedAt: time.Now(),
|
|
IntegrityOK: true,
|
|
FormatOK: true,
|
|
UsabilityOK: true,
|
|
Issues: []string{},
|
|
}
|
|
|
|
// Verify key hash
|
|
if secureData.KeyHash == "" {
|
|
result.IntegrityOK = false
|
|
result.Issues = append(result.Issues, "missing key hash")
|
|
}
|
|
|
|
// Test key usability by performing a test encryption/decryption
|
|
testData := []byte("test encryption data")
|
|
if err := km.testKeyUsability(secureData, testData); err != nil {
|
|
result.UsabilityOK = false
|
|
result.Issues = append(result.Issues, fmt.Sprintf("key usability test failed: %v", err))
|
|
}
|
|
|
|
if len(result.Issues) > 0 {
|
|
result.OverallResult = "failed"
|
|
} else {
|
|
result.OverallResult = "passed"
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// KeyVerificationResult represents the result of key verification
|
|
type KeyVerificationResult struct {
|
|
KeyID string `json:"key_id"`
|
|
VerifiedAt time.Time `json:"verified_at"`
|
|
IntegrityOK bool `json:"integrity_ok"`
|
|
FormatOK bool `json:"format_ok"`
|
|
UsabilityOK bool `json:"usability_ok"`
|
|
OverallResult string `json:"overall_result"`
|
|
Issues []string `json:"issues"`
|
|
}
|
|
|
|
// testKeyUsability tests if a key can be used for encryption/decryption
|
|
func (km *KeyManager) testKeyUsability(secureData *SecureKeyData, testData []byte) error {
|
|
// In production, implement actual encryption/decryption test
|
|
// For now, just verify the key format
|
|
if len(secureData.EncryptedKey) == 0 {
|
|
return fmt.Errorf("empty key data")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// BackupKeys creates a backup of keys matching the criteria
|
|
func (km *KeyManager) BackupKeys(criteria *BackupCriteria) (*KeyBackup, error) {
|
|
km.mu.RLock()
|
|
defer km.mu.RUnlock()
|
|
|
|
return km.keyStore.BackupKeys(criteria)
|
|
}
|
|
|
|
// RestoreKeys restores keys from a backup
|
|
func (km *KeyManager) RestoreKeys(backup *KeyBackup) error {
|
|
km.mu.Lock()
|
|
defer km.mu.Unlock()
|
|
|
|
return km.keyStore.RestoreKeys(backup)
|
|
}
|
|
|
|
// enforceSecurityConfig enforces SecurityConfig policies and schedules key rotation
|
|
func (km *KeyManager) enforceSecurityConfig() error {
|
|
if !km.config.Security.AuditLogging {
|
|
// Log warning if audit logging is disabled
|
|
km.logSecurityWarning("audit_logging_disabled", "Audit logging is disabled in SecurityConfig", map[string]interface{}{
|
|
"security_risk": "high",
|
|
"recommendation": "Enable audit logging for compliance and security monitoring",
|
|
})
|
|
}
|
|
|
|
// Enforce key rotation intervals
|
|
if km.config.Security.KeyRotationDays > 0 {
|
|
rotationInterval := time.Duration(km.config.Security.KeyRotationDays) * 24 * time.Hour
|
|
|
|
// Schedule key rotation for all roles
|
|
roles := config.GetPredefinedRoles()
|
|
for roleName := range roles {
|
|
policy := &KeyRotationPolicy{
|
|
RotationInterval: rotationInterval,
|
|
MaxKeyAge: rotationInterval + (7 * 24 * time.Hour), // Grace period
|
|
AutoRotate: true,
|
|
GracePeriod: 7 * 24 * time.Hour,
|
|
RequireQuorum: false,
|
|
MinQuorumSize: 1,
|
|
}
|
|
|
|
if err := km.rotationScheduler.ScheduleKeyRotation(roleName, policy); err != nil {
|
|
km.logSecurityWarning("key_rotation_schedule_failed",
|
|
fmt.Sprintf("Failed to schedule key rotation for role %s", roleName),
|
|
map[string]interface{}{
|
|
"role": roleName,
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
}
|
|
|
|
// Start the rotation scheduler
|
|
if err := km.rotationScheduler.Start(); err != nil {
|
|
return fmt.Errorf("failed to start key rotation scheduler: %w", err)
|
|
}
|
|
|
|
// Check for keys approaching rotation
|
|
go km.monitorKeyRotationDue()
|
|
} else {
|
|
km.logSecurityWarning("key_rotation_disabled", "Key rotation is disabled in SecurityConfig", map[string]interface{}{
|
|
"security_risk": "critical",
|
|
"recommendation": "Set KeyRotationDays to enable automatic key rotation",
|
|
})
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// monitorKeyRotationDue monitors for keys that are due for rotation
|
|
func (km *KeyManager) monitorKeyRotationDue() {
|
|
ticker := time.NewTicker(24 * time.Hour) // Check daily
|
|
defer ticker.Stop()
|
|
|
|
for range ticker.C {
|
|
km.checkKeysForRotation()
|
|
}
|
|
}
|
|
|
|
// checkKeysForRotation checks all keys and generates warnings for keys due for rotation
|
|
func (km *KeyManager) checkKeysForRotation() {
|
|
allKeys, err := km.keyStore.ListKeys(&KeyFilter{Status: KeyStatusActive})
|
|
if err != nil {
|
|
km.logSecurityWarning("key_check_failed", "Failed to check keys for rotation", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
rotationInterval := time.Duration(km.config.Security.KeyRotationDays) * 24 * time.Hour
|
|
warningThreshold := rotationInterval - (7 * 24 * time.Hour) // Warn 7 days before
|
|
|
|
for _, keyMeta := range allKeys {
|
|
keyAge := time.Since(keyMeta.CreatedAt)
|
|
|
|
if keyAge >= rotationInterval {
|
|
// Key is overdue for rotation
|
|
km.logKeyRotationWarning("key_rotation_overdue", keyMeta.KeyID, keyMeta.RoleID, map[string]interface{}{
|
|
"key_age_days": int(keyAge.Hours() / 24),
|
|
"rotation_due_days_ago": int((keyAge - rotationInterval).Hours() / 24),
|
|
"severity": "critical",
|
|
})
|
|
} else if keyAge >= warningThreshold {
|
|
// Key is approaching rotation
|
|
km.logKeyRotationWarning("key_rotation_due_soon", keyMeta.KeyID, keyMeta.RoleID, map[string]interface{}{
|
|
"key_age_days": int(keyAge.Hours() / 24),
|
|
"rotation_due_in_days": int((rotationInterval - keyAge).Hours() / 24),
|
|
"severity": "warning",
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// logSecurityWarning logs a security warning event
|
|
func (km *KeyManager) logSecurityWarning(warningType, message string, metadata map[string]interface{}) {
|
|
if km.auditLogger == nil {
|
|
return
|
|
}
|
|
|
|
event := &SecurityEvent{
|
|
EventID: fmt.Sprintf("security_warning_%s_%d", warningType, time.Now().Unix()),
|
|
EventType: "security_warning",
|
|
Timestamp: time.Now(),
|
|
UserID: km.config.Agent.ID,
|
|
Resource: "key_manager",
|
|
Action: warningType,
|
|
Outcome: "warning",
|
|
RiskLevel: "high",
|
|
Details: metadata,
|
|
}
|
|
event.Details["warning_message"] = message
|
|
|
|
km.auditLogger.LogSecurityEvent(event)
|
|
}
|
|
|
|
// logKeyRotationWarning logs a key rotation warning event
|
|
func (km *KeyManager) logKeyRotationWarning(warningType, keyID, roleID string, metadata map[string]interface{}) {
|
|
if km.auditLogger == nil {
|
|
return
|
|
}
|
|
|
|
event := &KeyRotationEvent{
|
|
EventID: fmt.Sprintf("%s_%s_%d", warningType, keyID, time.Now().Unix()),
|
|
Timestamp: time.Now(),
|
|
RotatedRoles: []string{roleID},
|
|
InitiatedBy: "key_manager_monitor",
|
|
Reason: warningType,
|
|
Success: false, // Warning, not actual rotation
|
|
ErrorMessage: fmt.Sprintf("Key rotation warning: %s", warningType),
|
|
}
|
|
|
|
km.auditLogger.LogKeyRotation(event)
|
|
}
|
|
|
|
// GetSecurityStatus returns the overall security status of the key management system
|
|
func (km *KeyManager) GetSecurityStatus() *KeyManagementSecurityStatus {
|
|
km.mu.RLock()
|
|
defer km.mu.RUnlock()
|
|
|
|
status := &KeyManagementSecurityStatus{
|
|
CheckedAt: time.Now(),
|
|
OverallHealth: "healthy",
|
|
ActiveKeys: 0,
|
|
ExpiredKeys: 0,
|
|
RevokedKeys: 0,
|
|
PendingRotations: 0,
|
|
SecurityScore: 0.95,
|
|
Issues: []string{},
|
|
Recommendations: []string{},
|
|
}
|
|
|
|
// Get all keys and analyze their status
|
|
allKeys, err := km.keyStore.ListKeys(&KeyFilter{IncludeMetadata: true})
|
|
if err != nil {
|
|
status.Issues = append(status.Issues, fmt.Sprintf("failed to retrieve keys: %v", err))
|
|
status.OverallHealth = "degraded"
|
|
return status
|
|
}
|
|
|
|
for _, key := range allKeys {
|
|
switch key.Status {
|
|
case KeyStatusActive:
|
|
status.ActiveKeys++
|
|
case KeyStatusExpired:
|
|
status.ExpiredKeys++
|
|
case KeyStatusRevoked:
|
|
status.RevokedKeys++
|
|
}
|
|
|
|
// Check for keys approaching expiration
|
|
if key.ExpiresAt != nil && time.Until(*key.ExpiresAt) < 7*24*time.Hour {
|
|
status.PendingRotations++
|
|
}
|
|
}
|
|
|
|
// Calculate security score based on key health
|
|
if status.ExpiredKeys > 0 {
|
|
status.SecurityScore -= 0.1
|
|
status.Issues = append(status.Issues, fmt.Sprintf("%d expired keys found", status.ExpiredKeys))
|
|
status.Recommendations = append(status.Recommendations, "Rotate expired keys immediately")
|
|
}
|
|
|
|
if status.PendingRotations > 0 {
|
|
status.SecurityScore -= 0.05
|
|
status.Recommendations = append(status.Recommendations, "Schedule key rotations for expiring keys")
|
|
}
|
|
|
|
if status.SecurityScore < 0.8 {
|
|
status.OverallHealth = "degraded"
|
|
} else if status.SecurityScore < 0.9 {
|
|
status.OverallHealth = "warning"
|
|
}
|
|
|
|
return status
|
|
}
|
|
|
|
// KeyManagementSecurityStatus represents the security status of key management
|
|
type KeyManagementSecurityStatus struct {
|
|
CheckedAt time.Time `json:"checked_at"`
|
|
OverallHealth string `json:"overall_health"` // healthy, warning, degraded, critical
|
|
ActiveKeys int `json:"active_keys"`
|
|
ExpiredKeys int `json:"expired_keys"`
|
|
RevokedKeys int `json:"revoked_keys"`
|
|
PendingRotations int `json:"pending_rotations"`
|
|
SecurityScore float64 `json:"security_score"` // 0.0 to 1.0
|
|
Issues []string `json:"issues"`
|
|
Recommendations []string `json:"recommendations"`
|
|
} |