# 07c. KEY VAULT Y CRIPTOGRAFÍA **Manual de Arquitectura Técnica — Documento 07c** **Versión:** 1.2 **Dependencia:** `S-CONTRACT.md` **Estado:** Enterprise Standard --- # C.1 Introducción El Key Vault es el **tercer espacio de almacenamiento** del ecosistema, separado de: - **Libro Mayor (SFE)**: Datos de negocio - **Almacenamiento de archivos**: Hostinger/S3 - **Key Vault**: Llaves, credenciales y secretos ``` ┌─────────────────────────────────────────────────────────────────┐ │ PRINCIPIO DE SEPARACIÓN │ │ │ │ "Las llaves nunca viajan con los datos que protegen" │ └─────────────────────────────────────────────────────────────────┘ ``` --- # C.2 Arquitectura del Key Vault ## C.2.1 Componentes ``` ┌─────────────────────────────────────────────────────────────────┐ │ KEY VAULT │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ SECRETS │ │ KEYS │ │ CERTS │ │ │ │ STORE │ │ STORE │ │ STORE │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ │ │ │ └──────────────────┼──────────────────┘ │ │ │ │ │ ┌──────┴──────┐ │ │ │ ACCESS │ │ │ │ CONTROL │ │ │ └──────┬──────┘ │ │ │ │ │ ┌──────┴──────┐ │ │ │ AUDIT LOG │ │ │ └─────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ## C.2.2 Tipos de Secretos | Tipo | Descripción | Ejemplo | Rotación | |------|-------------|---------|----------| | **API_KEY** | Llaves de APIs externas | OpenRouter, Groq | 90 días | | **ENCRYPTION_KEY** | Llaves de cifrado de datos | AES-256 keys | 365 días | | **DB_CREDENTIAL** | Credenciales de bases de datos | NocoDB, PostgreSQL | 30 días | | **SERVICE_TOKEN** | Tokens de servicios internos | n8n, Nextcloud | 7 días | | **USER_SECRET** | Secretos específicos de usuario | OAuth tokens | Variable | | **CERTIFICATE** | Certificados TLS/mTLS | SSL certs | 90 días | | **SIGNING_KEY** | Llaves de firma digital | JWT signing | 180 días | --- # C.3 Modelo de Datos ## C.3.1 Tabla VAULT_SECRETS ```sql CREATE TABLE VAULT_SECRETS ( -- ═══════════════════════════════════════════════════════════ -- IDENTIFICACIÓN -- ═══════════════════════════════════════════════════════════ id UUID PRIMARY KEY DEFAULT gen_random_uuid(), secret_id VARCHAR(100) UNIQUE NOT NULL, version INTEGER DEFAULT 1, -- ═══════════════════════════════════════════════════════════ -- CLASIFICACIÓN -- ═══════════════════════════════════════════════════════════ secret_type VARCHAR(50) NOT NULL, category VARCHAR(50), tags VARCHAR(50)[], -- ═══════════════════════════════════════════════════════════ -- VALOR (CIFRADO) -- ═══════════════════════════════════════════════════════════ encrypted_value BYTEA NOT NULL, encryption_algorithm VARCHAR(50) DEFAULT 'AES-256-GCM', key_encryption_key_id VARCHAR(100), nonce BYTEA, -- ═══════════════════════════════════════════════════════════ -- METADATA -- ═══════════════════════════════════════════════════════════ description TEXT, owner_id UUID, owner_type VARCHAR(50), -- ═══════════════════════════════════════════════════════════ -- CICLO DE VIDA -- ═══════════════════════════════════════════════════════════ status VARCHAR(20) DEFAULT 'ACTIVE', created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), expires_at TIMESTAMPTZ, last_rotated_at TIMESTAMPTZ, rotation_interval_days INTEGER, -- ═══════════════════════════════════════════════════════════ -- ACCESO -- ═══════════════════════════════════════════════════════════ access_policy JSONB DEFAULT '{}', allowed_modules VARCHAR(50)[], allowed_players UUID[], -- ═══════════════════════════════════════════════════════════ -- AUDITORÍA -- ═══════════════════════════════════════════════════════════ access_count INTEGER DEFAULT 0, last_accessed_at TIMESTAMPTZ, last_accessed_by VARCHAR(100), -- ═══════════════════════════════════════════════════════════ -- CONSTRAINTS -- ═══════════════════════════════════════════════════════════ CONSTRAINT valid_status CHECK ( status IN ('ACTIVE', 'DISABLED', 'EXPIRED', 'ROTATING', 'DELETED') ), CONSTRAINT valid_type CHECK ( secret_type IN ('API_KEY', 'ENCRYPTION_KEY', 'DB_CREDENTIAL', 'SERVICE_TOKEN', 'USER_SECRET', 'CERTIFICATE', 'SIGNING_KEY') ) ); -- Índices CREATE INDEX idx_vault_secret_id ON VAULT_SECRETS(secret_id); CREATE INDEX idx_vault_type ON VAULT_SECRETS(secret_type); CREATE INDEX idx_vault_status ON VAULT_SECRETS(status); CREATE INDEX idx_vault_expires ON VAULT_SECRETS(expires_at) WHERE expires_at IS NOT NULL; CREATE INDEX idx_vault_owner ON VAULT_SECRETS(owner_id); ``` ## C.3.2 Tabla VAULT_ACCESS_LOG ```sql CREATE TABLE VAULT_ACCESS_LOG ( -- ═══════════════════════════════════════════════════════════ -- IDENTIFICACIÓN -- ═══════════════════════════════════════════════════════════ id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- ═══════════════════════════════════════════════════════════ -- SECRETO ACCEDIDO -- ═══════════════════════════════════════════════════════════ secret_id VARCHAR(100) NOT NULL, secret_version INTEGER, -- ═══════════════════════════════════════════════════════════ -- CONTEXTO DE ACCESO -- ═══════════════════════════════════════════════════════════ trace_id UUID, step_id UUID, module_name VARCHAR(50), operation VARCHAR(50) NOT NULL, -- ═══════════════════════════════════════════════════════════ -- ACTOR -- ═══════════════════════════════════════════════════════════ accessor_type VARCHAR(50) NOT NULL, accessor_id VARCHAR(100) NOT NULL, accessor_ip INET, -- ═══════════════════════════════════════════════════════════ -- RESULTADO -- ═══════════════════════════════════════════════════════════ status VARCHAR(20) NOT NULL, error_code VARCHAR(50), error_message TEXT, -- ═══════════════════════════════════════════════════════════ -- TEMPORALIDAD -- ═══════════════════════════════════════════════════════════ timestamp TIMESTAMPTZ DEFAULT NOW(), duration_ms INTEGER, -- ═══════════════════════════════════════════════════════════ -- SEGURIDAD -- ═══════════════════════════════════════════════════════════ encryption_profile VARCHAR(20), purpose TEXT, -- ═══════════════════════════════════════════════════════════ -- CONSTRAINTS -- ═══════════════════════════════════════════════════════════ CONSTRAINT valid_operation CHECK ( operation IN ('READ', 'CREATE', 'UPDATE', 'DELETE', 'ROTATE', 'ENABLE', 'DISABLE', 'LIST', 'DECRYPT', 'ENCRYPT') ), CONSTRAINT valid_access_status CHECK ( status IN ('SUCCESS', 'DENIED', 'ERROR', 'EXPIRED', 'NOT_FOUND') ), CONSTRAINT valid_accessor CHECK ( accessor_type IN ('MODULE', 'PLAYER', 'SYSTEM', 'ADMIN', 'SERVICE') ) ); -- Índices para auditoría eficiente CREATE INDEX idx_vault_log_secret ON VAULT_ACCESS_LOG(secret_id); CREATE INDEX idx_vault_log_trace ON VAULT_ACCESS_LOG(trace_id) WHERE trace_id IS NOT NULL; CREATE INDEX idx_vault_log_timestamp ON VAULT_ACCESS_LOG(timestamp); CREATE INDEX idx_vault_log_accessor ON VAULT_ACCESS_LOG(accessor_type, accessor_id); CREATE INDEX idx_vault_log_denied ON VAULT_ACCESS_LOG(status, timestamp) WHERE status = 'DENIED'; ``` ## C.3.3 Tabla VAULT_ENCRYPTION_KEYS (KEK) ```sql CREATE TABLE VAULT_ENCRYPTION_KEYS ( -- ═══════════════════════════════════════════════════════════ -- IDENTIFICACIÓN -- ═══════════════════════════════════════════════════════════ id UUID PRIMARY KEY DEFAULT gen_random_uuid(), key_id VARCHAR(100) UNIQUE NOT NULL, version INTEGER DEFAULT 1, -- ═══════════════════════════════════════════════════════════ -- PROPIEDADES -- ═══════════════════════════════════════════════════════════ algorithm VARCHAR(50) NOT NULL DEFAULT 'AES-256-GCM', key_size_bits INTEGER NOT NULL DEFAULT 256, purpose VARCHAR(50) NOT NULL, -- ═══════════════════════════════════════════════════════════ -- VALOR (protegido por Master Key externa) -- ═══════════════════════════════════════════════════════════ encrypted_key_material BYTEA NOT NULL, master_key_ref VARCHAR(100), -- ═══════════════════════════════════════════════════════════ -- CICLO DE VIDA -- ═══════════════════════════════════════════════════════════ status VARCHAR(20) DEFAULT 'ACTIVE', created_at TIMESTAMPTZ DEFAULT NOW(), activated_at TIMESTAMPTZ, expires_at TIMESTAMPTZ, deactivated_at TIMESTAMPTZ, -- ═══════════════════════════════════════════════════════════ -- CONSTRAINTS -- ═══════════════════════════════════════════════════════════ CONSTRAINT valid_kek_status CHECK ( status IN ('PENDING', 'ACTIVE', 'DEACTIVATED', 'DESTROYED') ), CONSTRAINT valid_purpose CHECK ( purpose IN ('DATA_ENCRYPTION', 'SECRET_ENCRYPTION', 'SIGNING', 'KEY_WRAPPING') ) ); ``` --- # C.4 Perfiles de Cifrado ## C.4.1 Perfil NONE ```yaml profile: NONE description: "Sin cifrado gestionado por el sistema" transport: TLS 1.3 (obligatorio) at_rest: Sin cifrar use_cases: - Datos públicos - Logs técnicos no sensibles - Métricas de rendimiento key_vault: No requerido ``` ## C.4.2 Perfil E2E_BASIC ```yaml profile: E2E_BASIC description: "Cifrado estándar para datos internos" transport: TLS 1.3 at_rest: algorithm: AES-256-GCM key_derivation: PBKDF2-SHA256 key_rotation: Anual use_cases: - Documentos internos - Emails corporativos - Datos de configuración key_vault: required: true key_type: ENCRYPTION_KEY access_policy: module_based ``` ## C.4.3 Perfil E2E_STRICT ```yaml profile: E2E_STRICT description: "Cifrado fuerte para datos sensibles" transport: mTLS (mutual TLS) at_rest: algorithm: AES-256-GCM key_derivation: Argon2id key_rotation: Trimestral envelope_encryption: true additional: - PII masking antes de procesar - Audit logging obligatorio - Zero-knowledge donde sea posible use_cases: - PII (datos personales) - Datos financieros - Información médica - Biometría key_vault: required: true key_type: ENCRYPTION_KEY access_policy: per_record key_isolation: true ``` --- # C.5 Operaciones del Key Vault ## C.5.1 URIs del Key Vault El contrato común usa referencias URI al Key Vault: ``` kv://production/encryption/player_abc123 kv://production/api-keys/openrouter kv://staging/certificates/mtls-client ``` Formato: ``` kv://{environment}/{category}/{secret_id} ``` ## C.5.2 Integración con el Contrato En el request: ```json "security": { "encryption_profile": "E2E_STRICT", "data_sensitivity": "HIGH", "key_vault_ref": "kv://production/encryption/player_abc123" } ``` Alfred al procesar: 1. Extrae `key_vault_ref` 2. Solicita la llave al Key Vault (pasando `trace_id`) 3. Descifra el payload si viene cifrado 4. Procesa con GRACE 5. Cifra el resultado con la misma llave (o una derivada) 6. Almacena resultado cifrado --- # C.6 Flujo de Cifrado End-to-End ## C.6.1 Envelope Encryption ``` ┌─────────────────────────────────────────────────────────────────┐ │ ENVELOPE ENCRYPTION │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ │ │ │ Plaintext │ │ │ │ Data │ │ │ └──────┬──────┘ │ │ │ │ │ ▼ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ Data │◄───│ DEK │ (Data Encryption Key) │ │ │ Encrypted │ │ (efímera) │ │ │ └──────┬──────┘ └──────┬──────┘ │ │ │ │ │ │ │ ▼ │ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ │ DEK │◄───│ KEK │ │ │ │ │ Encrypted │ │ (Key Vault) │ │ │ │ └──────┬──────┘ └─────────────┘ │ │ │ │ │ │ ▼ ▼ │ │ ┌──────────────────────────────────────┐ │ │ │ Stored Package: │ │ │ │ - encrypted_data │ │ │ │ - encrypted_dek │ │ │ │ - kek_id │ │ │ │ - nonce │ │ │ │ - algorithm │ │ │ └──────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ## C.6.2 Implementación Python ```python import os from cryptography.hazmat.primitives.ciphers.aead import AESGCM import base64 class EnvelopeEncryption: """Implementa envelope encryption para E2E_STRICT.""" def __init__(self, key_vault_client): self.vault = key_vault_client async def encrypt(self, plaintext: bytes, kek_id: str) -> dict: """Cifra datos usando envelope encryption.""" # 1. Generar DEK efímera dek = os.urandom(32) data_nonce = os.urandom(12) # 2. Cifrar datos con DEK aesgcm = AESGCM(dek) encrypted_data = aesgcm.encrypt(data_nonce, plaintext, None) # 3. Obtener KEK y cifrar DEK kek = await self.vault.get_secret(kek_id) dek_nonce = os.urandom(12) kek_aesgcm = AESGCM(kek) encrypted_dek = kek_aesgcm.encrypt(dek_nonce, dek, kek_id.encode()) return { "encrypted_data": base64.b64encode(encrypted_data).decode(), "encrypted_dek": base64.b64encode(encrypted_dek).decode(), "data_nonce": base64.b64encode(data_nonce).decode(), "dek_nonce": base64.b64encode(dek_nonce).decode(), "kek_id": kek_id, "algorithm": "AES-256-GCM" } async def decrypt(self, package: dict) -> bytes: """Descifra datos usando envelope encryption.""" # 1. Obtener KEK kek = await self.vault.get_secret(package["kek_id"]) # 2. Descifrar DEK kek_aesgcm = AESGCM(kek) dek = kek_aesgcm.decrypt( base64.b64decode(package["dek_nonce"]), base64.b64decode(package["encrypted_dek"]), package["kek_id"].encode() ) # 3. Descifrar datos aesgcm = AESGCM(dek) return aesgcm.decrypt( base64.b64decode(package["data_nonce"]), base64.b64decode(package["encrypted_data"]), None ) ``` --- # C.7 Gestión de Llaves ## C.7.1 Jerarquía de Llaves ``` ┌─────────────────────────────────────────────────────────────────┐ │ JERARQUÍA DE LLAVES │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ MASTER KEY (MK) │ │ │ │ Almacenada fuera del sistema │ │ │ │ (HSM / KMS externo / Variable de entorno) │ │ │ └────────────────────────┬────────────────────────────────┘ │ │ │ │ │ ┌───────────────┼───────────────┐ │ │ ▼ ▼ ▼ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ KEK-DATA │ │ KEK-SECRETS │ │ KEK-SIGNING │ │ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ DEK-1..n │ │ API Keys │ │ JWT Keys │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ## C.7.2 Política de Rotación | Tipo de Llave | Rotación | Período de Gracia | Automatizable | |---------------|----------|-------------------|---------------| | Master Key | Anual | 30 días | No (manual) | | KEK | Semestral | 14 días | Sí | | DEK | Por sesión | N/A | Automático | | API Keys | 90 días | 7 días | Sí | --- # C.8 Control de Acceso ## C.8.1 Políticas de Acceso ```json { "secret_id": "openrouter-api-key", "access_policy": { "type": "MODULE_BASED", "rules": [ { "principal_type": "MODULE", "principals": ["CLASSIFIER", "SUMMARIZER", "OCR_CORE"], "operations": ["READ"], "conditions": { "require_trace_id": true } }, { "principal_type": "ADMIN", "principals": ["admin@tzzr.pro"], "operations": ["READ", "UPDATE", "ROTATE", "DELETE"], "conditions": { "require_mfa": true } } ], "default_deny": true } } ``` --- # C.9 Integración con SENTINEL ## C.9.1 Reglas de Auditoría ```yaml rules: KV-001: name: "Accesos denegados excesivos" severity: HIGH condition: "denied_count_1h > 5 per accessor" action: ALERT_SECURITY KV-002: name: "Secreto próximo a expirar" severity: MEDIUM condition: "expires_at < NOW() + 7 days" action: NOTIFY_ADMIN KV-003: name: "Rotación pendiente" severity: MEDIUM condition: "last_rotated_at < NOW() - rotation_interval" action: SCHEDULE_ROTATION ``` --- # C.10 Checklist de Implementación ## Infraestructura: - [ ] Crear tablas VAULT_SECRETS, VAULT_ACCESS_LOG, VAULT_ENCRYPTION_KEYS - [ ] Configurar Master Key (variable de entorno o KMS) - [ ] Generar KEKs iniciales - [ ] Configurar backup automático ## Integración: - [ ] Implementar cliente Key Vault - [ ] Integrar con ContractBuilder - [ ] Implementar envelope encryption ## Seguridad: - [ ] Configurar políticas de acceso - [ ] Habilitar logging de accesos - [ ] Configurar alertas SENTINEL - [ ] Establecer rotación automática --- **Fin del Documento 07c — Key Vault y Criptografía** *Referencia: `S-CONTRACT.md` v1.2*