Evidencia técnica de cumplimiento
Este documento demuestra, componente a componente, por qué y cómo la tecnología de ZetiSecret satisface controles concretos del ENS (RD 311/2022) y de ISO/IEC 27001:2022. Cada afirmación se acompaña de referencia al código fuente o a la configuración verificable.
Resumen ejecutivo
La arquitectura zero-knowledge de ZetiSecret proporciona garantías criptográficas estructurales: el servidor no puede leer los secretos almacenados porque no recibe la clave privada. Esto no es una medida de configuración que pueda desactivarse por error — es una consecuencia del protocolo HTTP (RFC 3986 §3.5). Este diseño satisface por defecto los controles más exigentes de cifrado y confidencialidad de ambos marcos.
1. Criptografía: ECDH P-256 + AES-GCM + PBKDF2
Cifrado híbrido en el navegador (WebCrypto API)
Por qué cumple: ENS mp.info.3 exige cifrado con algoritmos aprobados por el CCN para información de nivel MEDIO. ISO 27001 §8.24 requiere reglas de criptografía con gestión de claves. ECDH P-256 y AES-GCM 256-bit están aprobados por NIST (FIPS 186-4, SP 800-56A, FIPS 197, SP 800-38D) y reconocidos en la guía CCN-STIC-807. AES-GCM proporciona cifrado autenticado (AEAD): confidencialidad e integridad en una sola primitiva — equivale a cifrado + MAC. El IV aleatorio de 96 bits por secreto garantiza que nunca se reutiliza el nonce, requisito explícito de NIST SP 800-38D.
public/app.js · crypto.getRandomValues(new Uint8Array(12)) — IV de 96 bits aleatorio
public/app.js · crypto.subtle.encrypt({name:'AES-GCM', iv}, aesKey, plaintext) — cifrado autenticado
server.js · MIN_PASSPHRASE_ITERATIONS=310000 — iteraciones PBKDF2 ≥ OWASP 2024 (210k)
Protección de passphrase con PBKDF2-SHA-256
Por qué cumple: ENS op.acc.5 y ISO §8.5 exigen mecanismos de autenticación que resistan fuerza bruta. PBKDF2-HMAC-SHA-256 con salt único de 128 bits y ≥310.000 iteraciones hace prohibitivo el coste de ataque offline. La derivación ocurre en el navegador: el servidor recibe únicamente el hash derivado, nunca la passphrase en claro. El bloqueo temporal tras N intentos fallidos protege contra fuerza bruta online.
public/app.js · createPassphraseSalt() → crypto.getRandomValues(new Uint8Array(16)) — 128-bit salt
server.js · bloqueo temporal: UPDATE secrets SET locked_until = now() + lock_minutes tras N fallos
2. Zero-knowledge: clave privada solo en el fragment URL
Clave privada exclusivamente en el fragmento # de la URL
Por qué cumple: ENS mp.com.2 exige que solo los destinatarios autorizados puedan acceder a la información transmitida. ISO §8.12 requiere prevenir fugas de datos. La clave privada ECDH viaja solo en el fragmento #… del enlace. Por definición del protocolo HTTP (RFC 3986 §3.5), el fragment no se envía al servidor en ninguna petición HTTP — ni en el request inicial, ni en redirecciones. Esta es una garantía del protocolo, no de configuración. El servidor no puede acceder a la clave privada aunque quisiera: no hay code path por el que pueda recibirla.
public/app.js · const key = window.location.hash.slice(1) — leído del browser, sin petición al servidor
RFC 3986 §3.5 · "the fragment identifier is not sent with requests"
3. Transporte: TLS 1.2/1.3 + HSTS
HTTPS obligatorio con renovación automática (Let's Encrypt)
Por qué cumple: ENS mp.com.1 y mp.com.3 exigen cifrado del transporte para proteger confidencialidad, autenticidad e integridad de las comunicaciones. ISO §8.21 requiere seguridad en servicios de red. TLS 1.2+ con certificados Let's Encrypt garantiza: (a) cifrado del canal, (b) autenticación del servidor, (c) integridad del tráfico, (d) renovación automática evitando expiración. HSTS (max-age=31536000) previene ataques de downgrade a HTTP.
deploy/Caddyfile.example · TLS automático Let's Encrypt, reverse proxy a app:8090
Despliegue cloud · Azure App Service gestiona TLS 1.2+ y renovación de certificado
4. Segregación de red
Red interna Docker: app ↔ PostgreSQL sin exposición externa
Por qué cumple: ENS mp.com.4 y ISO §8.22 exigen segregación de redes para limitar el alcance de un compromiso. El servidor Node.js escucha únicamente en 127.0.0.1:8090 (loopback). PostgreSQL no expone puertos al host — solo accesible desde la red Docker interna (internal). Caddy es el único proceso con puertos 80/443 públicos. Un atacante que comprometa la capa de aplicación no puede acceder directamente a la base de datos desde Internet.
compose.prod.example.yml · redes internal (app↔postgres, sin internet) y proxy (caddy↔app)
compose.prod.example.yml · postgres sin ports: — inaccesible desde el host
Despliegue cloud · PostgreSQL en VNet privado (Azure), sin endpoint público
5. Seguridad de la aplicación web
Cabeceras HTTP: CSP estricta, HSTS, X-Frame-Options (Helmet.js)
Por qué cumple: ENS mp.s.2 e ISO §8.26 exigen protección de aplicaciones web expuestas. La política CSP sin unsafe-inline ni unsafe-eval es la mitigación más eficaz contra XSS. X-Frame-Options: DENY previene clickjacking. Cache-Control: no-store en API impide que datos del response queden en cachés intermediarias. Permissions-Policy restringe APIs del navegador innecesarias para la aplicación.
server.js · CSP: default-src 'self', sin CDNs externos, sin unsafe-inline
server.js · Permissions-Policy: geolocation=(), microphone=(), camera=(), payment=()
server.js · Cache-Control: no-store en middleware de API
Rate limiting por endpoint + bloqueo anti-fuerza-bruta
Por qué cumple: ENS op.acc.5 e ISO §8.5 exigen protección de mecanismos de autenticación contra fuerza bruta. El límite de 5 intentos de passphrase con bloqueo de 15 min impide ataques online sistemáticos. El rate limiting global y por endpoint reduce la viabilidad de ataques de enumeración y DoS a nivel de aplicación.
server.js · Creación: limit:60 / Consumo: limit:30 / Auth: limit:10 por 15 min
server.js · Passphrase: locked_until = now() + 15min tras 5 intentos fallidos
Validación de inputs y consultas SQL parametrizadas
Por qué cumple: ISO §8.28 exige codificación segura incluyendo prevención de inyecciones. Las consultas parametrizadas con $1, $2… eliminan completamente SQL injection (OWASP Top 10 #3). La validación positiva con regex explícito (allowlist de caracteres) es más segura que sanitización reactiva.
server.js · validateSlug(): regex [a-z0-9][a-z0-9-]{1,61}[a-z0-9]
server.js · todas las queries usan parámetros posicionales $1, $2 — sin concatenación de strings
Comparaciones timing-safe (crypto.timingSafeEqual)
Por qué cumple: Los ataques de timing por canal lateral son una técnica documentada (Crosby & Wallach, 2009) para extraer tokens de autenticación remotamente midiendo tiempos de respuesta. crypto.timingSafeEqual() garantiza tiempo de comparación constante independientemente del token, eliminando este vector. ISO §8.5 y §8.28 exigen implementaciones de autenticación resistentes a ataques conocidos.
server.js · aplicado a: ADMIN_TOKEN, MAINTENANCE_TOKEN, passphrase_hash, link_public_key_hash
6. Auditoría y logging
Logs JSON estructurados sin datos sensibles + tabla audit_events
Por qué cumple: ENS op.exp.8 e ISO §8.15 exigen registro de accesos y eventos de seguridad con detalle suficiente para auditoría. ENS mp.info.1 y RGPD Art. 25 (privacy by design) exigen minimización de datos personales. Las IPs son datos personales bajo el RGPD — hashearlas con SHA-256 antes de registrar permite auditoría sin almacenar PII. Los campos requestId (UUID) permiten correlacionar todos los eventos de una petición.
server.js · safePath(): sustituye IDs reales por :id en rutas
server.js · campos fijos: ts, level, event, requestId, ipHash, method, path, status, durationMs
server.js · tabla audit_events: event, request_id, org_id, secret_id_hash, ip_hash, metadata JSONB
server.js · webhook SIEM: AUDIT_WEBHOOK_URL — reenvío en tiempo real a Elasticsearch/Splunk/Slack
7. Ciclo de vida del dato: borrado atómico + TTL
Borrado transaccional en consumo + limpieza automática por TTL
Por qué cumple: ENS mp.info.6 e ISO §8.10 exigen eliminación segura y verificable cuando la información ya no es necesaria. RGPD Art. 5.1.e (limitación del plazo de conservación) exige que los datos no se conserven más tiempo del necesario. El borrado es atómico dentro de una transacción PostgreSQL: el secreto se destruye en el mismo instante en que es revelado — imposible segunda lectura incluso ante condición de carrera. La limpieza horaria garantiza que secretos no consumidos desaparecen al vencer su TTL.
server.js · ROLLBACK si cualquier operación falla — el secreto no se expone
server.js · deleteExpiredSecrets(): DELETE FROM secrets WHERE expires_at <= now()
server.js · job horario: setInterval(deleteExpiredSecrets, 60 * 60 * 1000)
8. Hardening de infraestructura
Contenedor Docker: usuario no privilegiado, filesystem read-only
Por qué cumple: ENS op.acc.6 e ISO §8.18 exigen principio de mínimo privilegio para procesos locales. Un proceso comprometido corriendo como UID 10001 (no root) con filesystem de solo lectura no puede instalar herramientas de ataque, modificar código ni persistir malware. no-new-privileges: true previene escalada de privilegios mediante setuid. ENS op.exp.6 e ISO §8.7 exigen protección contra código dañino: el filesystem inmutable imposibilita persistencia de malware.
compose.prod.example.yml · read_only: true, no-new-privileges: true
compose.prod.example.yml · mem_limit: 256m, cpus: '1', tmpfs: [/tmp]
Despliegue cloud · Azure App Service gestiona el SO como PaaS gestionado
9. Gestión de dependencias y vulnerabilidades
npm audit en CI + instalación reproducible con npm ci
Por qué cumple: ENS op.exp.4 e ISO §8.8 exigen gestión activa de vulnerabilidades técnicas. La integración de npm audit en CI automatiza la detección de CVEs en dependencias en cada commit — reduciendo el tiempo entre publicación de CVE y detección a horas. npm ci garantiza instalación reproducible desde package-lock.json, haciendo que la imagen de producción sea exactamente la auditada. ISO §5.21 (cadena de suministro TIC): la auditoría de dependencias es defensa ante supply chain attacks.
Dockerfile · npm ci --omit=dev — instalación reproducible, solo dependencias de producción
server.js · 4 dependencias de producción: express, express-rate-limit, helmet, pg
Tabla consolidada de controles
ENS RD 311/2022 — controles implementados
| Control ENS | Nombre | Evidencia |
|---|---|---|
| mp.info.3 | Cifrado | ECDH P-256 + AES-GCM 256-bit (WebCrypto, FIPS 197) |
| mp.com.2 | Confidencialidad | Clave privada en fragment URL — nunca llega al servidor (RFC 3986) |
| mp.com.3 | Autenticidad e integridad | AES-GCM (AEAD) + TLS + timingSafeEqual |
| mp.com.1 | Perímetro seguro (comunicaciones) | TLS 1.2+ con Caddy/Let's Encrypt, HSTS 1 año |
| mp.com.4 | Segregación de redes | Red Docker interna app↔postgres / VNet Azure |
| mp.if.7 | Perímetro seguro | Node.js en loopback, Caddy único punto público, PostgreSQL sin puerto externo |
| mp.s.2 | Protección de servicios web | Helmet CSP, rate limiting, input validation, no unsafe-inline |
| mp.info.1 | Datos de carácter personal | IPs hasheadas SHA-256, sin PII en logs |
| mp.info.6 | Limpieza de soportes | Borrado atómico en consumo + limpieza horaria TTL |
| mp.info.9 | Retención y destrucción | TTL configurable (1h–7d), borrado en consumo, no retención de secretos |
| op.acc.2 | Requisitos de acceso | Admin solo con token, Node.js solo en loopback, PostgreSQL en red interna |
| op.acc.5 | Mecanismo de autenticación | Token 256-bit, PBKDF2, bloqueo anti-fuerza-bruta, timingSafeEqual |
| op.acc.6 | Acceso local privilegiado | Docker usuario zetisecret UID 10001, read-only, no-new-privileges |
| op.exp.2 | Configuración de seguridad | Infraestructura como código, validate-config.js al arranque |
| op.exp.4 | Mantenimiento y actualizaciones | npm audit en CI, SLAs de remediación por CVSS |
| op.exp.6 | Protección frente a código dañino | Docker read-only, npm audit, imagen oficial verificada |
| op.exp.8 | Registro de actividad | Logs JSON estructurados + tabla audit_events + webhook SIEM |
| op.exp.9 | Registro de incidencias | audit_events persistente, request_id de correlación |
| op.pl.2 | Arquitectura de seguridad | Zero-knowledge documentado en security-model.md |
| op.pl.3 | Adquisición de componentes | npm audit, package-lock.json, npm ci |
ISO/IEC 27001:2022 — controles del Anexo A implementados
| Control ISO | Nombre | Evidencia |
|---|---|---|
| 5.14 | Transferencia de información | ECDH + fragment URL = clave privada nunca en tránsito hacia servidor |
| 5.21 | Seguridad en cadena de suministro TIC | npm audit, npm ci, imágenes Docker oficiales |
| 5.28 | Recopilación de evidencias | requestId UUID, audit_events con correlación completa |
| 8.5 | Autenticación segura | PBKDF2, timingSafeEqual, bloqueo brute-force, token 256-bit |
| 8.6 | Gestión de capacidad | Rate limiting, límites Docker (256MB RAM, 1 CPU) |
| 8.7 | Protección contra malware | npm audit CI, Docker read-only, imagen oficial |
| 8.8 | Gestión de vulnerabilidades técnicas | npm audit, SLAs por CVSS, proceso de remediación documentado |
| 8.9 | Gestión de la configuración | IaC Dockerfile/compose, validate-config.js, .env.example |
| 8.10 | Eliminación de información | Borrado atómico transaccional + TTL automático |
| 8.11 | Enmascaramiento de datos | SHA-256 de IPs y secret IDs en todos los logs |
| 8.12 | Prevención de fuga de datos | Fragment URL, Cache-Control: no-store, CSP estricta, sin datos en logs |
| 8.15 | Registro de actividades | Logs JSON + audit_events + webhook SIEM (3 capas) |
| 8.18 | Uso de programas privilegiados | Usuario UID 10001, sin shell, filesystem read-only |
| 8.19 | Instalación de software en producción | Imagen inmutable CI, npm ci, sin instalación en runtime |
| 8.20 | Seguridad en redes | Loopback bind, red Docker interna, VNet Azure |
| 8.21 | Seguridad de servicios de red | TLS 1.2+ con Let's Encrypt, HSTS, renovación automática |
| 8.22 | Segregación de redes | Redes Docker internal/proxy, PostgreSQL sin puerto externo |
| 8.24 | Uso de criptografía | ECDH P-256 + AES-GCM 256-bit (NIST FIPS 197) + PBKDF2-SHA-256 |
| 8.26 | Requisitos de seguridad de la aplicación | Helmet, rate limiting, validación inputs, borrado atómico documentados |
| 8.27 | Arquitectura segura del sistema | Zero-knowledge, mínimo privilegio, defensa en profundidad |
| 8.28 | Codificación segura | SQL parametrizado, timingSafeEqual, manejo de errores genérico |
| 8.29 | Pruebas de seguridad | Tests API (supertest), tests E2E (Playwright), npm audit en CI |
| 8.3 | Restricción de acceso a la información | Secretos solo devueltos en consumo (con borrado simultáneo) |
| 8.9 | Gestión de configuración | Infraestructura como código, secrets en env vars, nunca en imagen |
| 5.37 | Procedimientos de operación documentados | secure-deployment.md, production-checklist.md, monitoring.md |
Brechas conocidas y roadmap
- MFA para administradores — ENS
op.acc.5, ISO8.5. El acceso administrativo usa token bearer sin segundo factor. Planificado. - Análisis de riesgos formal (MAGERIT) — ENS
op.pl.1, ISO6.1.2. Los riesgos están identificados pero no formalizados con metodología documentada. Pendiente. - Política de seguridad aprobada por dirección — ENS
org.1, ISO5.1. Existe documentación técnica pero no política formal firmada. Pendiente. - WORM / object-lock para logs de auditoría — ENS
op.exp.10, ISO5.33. Los logs en audit_events no tienen protección contra borrado. Planificado. - SAST/DAST en pipeline CI — ISO
8.25,8.29. Actualmente solonpm audit(análisis de dependencias). Planificado. - Pentest externo y revisión criptográfica — ENS
op.pl.1, ISO5.35. La arquitectura criptográfica no ha sido revisada por tercero independiente. Planificado. - Contratos de seguridad con proveedores (DPA, SLAs) — ENS
op.ext.1, ISO5.19. Proveedores documentados en supplier-register.md pero sin contratos formalizados con cláusulas ENS/ISO. Pendiente.