
Monté un sandbox público del Sistema de Finanzas Abiertas de Chile, y Keycloak solo no alcanzaba
Chile tiene norma de finanzas abiertas y no tiene un sandbox público donde probarla. Monté uno completo y no oficial, fiel al Anexo Técnico: directorio de participantes, banco, servidor de autorización y aplicación solicitante, con PAR obligatorio, objeto de petición cifrado, mTLS y RAR en el token. Keycloak 26 cubre buena parte por configuración, pero el modelo de consentimiento que Chile eligió no existe en el producto: hubo que escribirlo entero.
De quién son tus datos
Antes de la norma y del perfil técnico conviene fijar lo único que hay que entender: el dato ya era tuyo. Con finanzas abiertas cambia otra cosa: quién decide adónde va.
Antes
Con finanzas abiertas
Eso es todo el sistema en una frase. El resto de este post es la maquinaria que hace que esa frase sea cierta y no un folleto: cómo se prueba que fuiste tú, cómo viaja el permiso sin que nadie lo pueda ensanchar por el camino, y cómo se corta cuando dices que se corte.
Chile lleva desde 2023 con una ley de finanzas abiertas y desde 2024 con la norma que la regula. Hay plazos, hay obligaciones y hay un anexo técnico con el perfil de seguridad exacto que cada participante tiene que implementar.
Sandbox oficial hay: se llama ATENA y vive en el portal del desarrollador. No es público: entran los participantes inscritos ante la Comisión, con sus trámites y sus credenciales. No se abre para mirarle las costuras a las tres de la mañana, y su código no se lee.
Eso es un problema concreto para quien todavía no es participante y tiene que construir para llegar a serlo. El anexo describe un sistema de cuatro actores, y tres de ellos son otras instituciones. Sin credenciales no se puede probar un proveedor de servicios de información contra un banco, ni un banco contra un directorio de participantes, porque ninguno de los dos está expuesto fuera de ahí.
Así que lo monté entero. Cuatro actores, seis subdominios, y una corrida real de punta a punta que empieza en el descubrimiento y termina con tres cuentas leídas de un banco de fantasía.
Lo que quedó es un sandbox público y no oficial donde se pueden probar las funcionalidades que exige la CMF: PAR obligatorio con el objeto de petición firmado y cifrado, registro dinámico contra una declaración de software, mTLS con token ligado al certificado, consentimiento con su detalle y RAR viajando hasta el token.
Y la conclusión corta, que es la que me habría ahorrado dos días: Keycloak cubre buena parte del perfil por configuración, pero solo no llega. Lo que falta es justo la pieza del consentimiento, y no se tapa con un parche.
Qué es esto y qué no es
Conviene ser preciso, porque «sandbox» se usa para cosas muy distintas.
Lo que sí es. Una implementación fiel al Anexo Técnico en lo que se ve por el cable: los mensajes, el orden, los algoritmos y los rechazos son los que la norma pide. Cualquiera puede levantarlo y probar contra él un PSBI o un banco propio.
Lo que no es. Un participante del sistema real. Lo que lo separa no es protocolo, es cadena de confianza: la PKI es propia y autofirmada en vez de la CA oficial, el Directorio es el que escribí yo en vez del de la Comisión, los datos son sintéticos y no hay inscripción ni certificación de por medio. Eso es exactamente lo que se necesita para probar y exactamente lo que impide operar.
Dicho de otro modo: la parte administrativa está ausente a propósito. La técnica no.
Sandbox SFA · placa de características
✓ Aquí puedes
- mTLS con certificado de cliente, y el token ligado a él
- PAR con el objeto de petición firmado y cifrado
- Registro dinámico contra una declaración de software firmada
- El RAR chileno con sus cuatro reglas de validación
- Grant Management: crear, consultar, revocar y auditar
- Romperlo a propósito y leer el error exacto que devuelve
✗ Aquí nunca
- Apuntar credenciales o certificados reales
- Mover datos de personas de verdad
- Presentarlo como participante del sistema
- Confiar en su PKI: es propia y autofirmada
- Contar con que esté disponible
Qué es el SFA y por qué su perfil es distinto
El Sistema de Finanzas Abiertas obliga a que un banco entregue los datos de un cliente a otro proveedor cuando ese cliente lo autoriza. La parte interesante para quien implementa no es la idea, que ya existe en Europa y Brasil, sino el perfil técnico que la Comisión eligió: FAPI 2.0 con petición empujada obligatoria, objeto de petición firmado y cifrado, mTLS con token ligado al certificado, y consentimiento expresado como authorization_details del RFC 9396.
Ese último punto es el que cambia todo. En OAuth clásico el permiso es una palabra: accounts. Con RAR el permiso es un objeto que nombra las cuentas, las acciones y el nivel. Un token que dice accounts no permite auditar nada; uno que trae las tres cuentas por identificador sí.
Hay un quinto actor que la norma da por sentado y que en la práctica todavía no existe de forma abierta: el Directorio de Participantes. Es quien dice qué instituciones están activas, quién emite las declaraciones de software que habilitan el registro dinámico de clientes y quién publica las claves públicas contra las que se verifican las firmas. Sin él, cada participante tendría que cablear a los demás a mano, que es exactamente lo que el sistema quiere evitar. Lo escribí también, con las rutas que aparecen en la colección de Postman oficial.
El recorrido de la persona
Este es el recorrido funcional: lo que hace y ve quien autoriza. La figura camina de un edificio a otro, y el paso que más importa es el quinto, porque ahí la persona compara lo que pidió la aplicación con lo que le muestra su banco. Esa comparación es la defensa del sistema entero: si las dos pantallas no coinciden, algo va mal y quien lo nota es ella.
El supervisor está arriba mirando, que es exactamente su papel: no manda ni recibe mensajes. Y el recuadro punteado importa, porque el servidor de autorización pertenece al banco, no es un tercero.
correo y contraseña
Llegas a la aplicación que quiere mostrarte tus cuentas y entras con tu cuenta de ahí.
No es un trámite de adorno: la norma condiciona que el consentimiento valga a que el PSBI haya autenticado a quien lo otorga.
permisos · institución · finalidad · vigencia
Antes de ir a ninguna parte lees qué datos pide, a qué banco, para qué servicio y por cuánto tiempo. Y aceptas.
La norma exige informarte esas cuatro cosas de forma precisa y clara antes de pedirte el consentimiento.
te vas al sitio de tu banco
La aplicación te dice que a continuación pasas al sitio de tu banco.
Tu clave del banco no se escribe aquí, y eso es exactamente lo que cambia con finanzas abiertas.
te autenticas donde siempre
Entras con las credenciales de tu banco, en el sitio de tu banco.
La aplicación que pide los datos nunca las ve. Se pide autenticación fresca aunque tuvieras la sesión abierta.
lo mismo que pidió el PSBI
Tu banco te muestra qué se pide, para qué y por cuánto, y eliges sobre qué cuentas. Tu trabajo aquí es ver que calce con lo que leíste antes.
Al banco le está prohibido alterar la solicitud o pedirte algo adicional. Si lo que ves aquí no es lo que aceptaste allá, esa es la señal.
código de seis dígitos
Llega un código a tu correo y lo escribes.
La autenticación reforzada la pone el banco, con su marca. Sin ella no hay consentimiento otorgado.
Authorised · quién y cuándo
Recién ahora el consentimiento existe como autorización, con tu identidad y la hora registradas.
El expediente se había abierto al empezar, pendiente. Lo que tu confirmación hace es otorgarlo, que es lo que la norma reputa como consentimiento.
código de autorización
El navegador te devuelve al sitio desde donde saliste.
La aplicación comprueba que la respuesta le corresponde antes de hacer nada con ella.
solo las cuentas que elegiste
Aparecen las cuentas y los movimientos, y solo los de las cuentas que marcaste.
El permiso que viaja nombra esas cuentas y nada más, así que ni el banco ni la aplicación pueden estirarlo.
El mismo recorrido, por debajo
Eso es lo que se ve. Debajo, en cada uno de esos pasos, ocurre lo que hace que esto sea el Sistema de Finanzas Abiertas y no una integración cualquiera: certificados, firmas, un registro dinámico y una petición empujada. Aquí ya no hay nadie caminando, porque aquí hablan máquinas. Y entra el Directorio, que es quien sostiene la confianza entre los demás.
GET /public/v1/participants
El PSBI pregunta al Directorio quién está activo y dónde vive cada API.
Ningún participante se cablea. Si el Directorio marca a una institución como Inactiva, deja de descubrirse.
JWS PS256 · iss, jti, exp
El Directorio emite una SSA firmada que acredita al PSBI y sus URLs.
Es la credencial que sustituye al trámite, y solo el Directorio puede firmarla.
POST /register · software_statement
El PSBI se registra en el servidor de autorización presentando esa declaración.
Antes de crear el cliente se verifica firma, vigencia, jti y que el participante siga Activo en el Directorio.
certificado de transporte
Todo el tráfico entre máquinas presenta el certificado de transporte del participante.
Sin certificado no hay canje de token ni acceso a datos. El borde lo traduce a PEM codificado en URL, lo único que el servidor acepta.
JWE(JWS) · PS256 + RSA-OAEP/A256GCM
La petición de autorización se firma y después se cifra.
El orden no es intercambiable: cifrar antes de firmar dejaría una firma sobre texto cifrado, que no prueba nada del contenido.
request_uri + grantId
El adaptador valida el RAR chileno, acuña el grantId y reenvía la petición intacta.
Reenviarla sin tocar es lo que mantiene válida la firma. Un RAR que incumple no llega nunca a Keycloak.
client_request_param_authorization_details
Un mapeador propio copia al token el authorization_details concedido.
El servidor acepta el RAR y no lo emite. Sin esas líneas el banco recibe un token que no dice sobre qué se consintió.
cnf.x5t#S256
El token sale con la huella del certificado que lo pidió.
Es lo que hace inútil un token robado: quien lo presente tiene que probar que posee esa clave privada.
x-consent-id + Bearer
El banco lee el grant, introspecciona el token y compara antes de responder.
La Grant Management API es la fuente de verdad, así que una revocación corta el acceso ahora y no cuando caduque una caché.
POST /grants/{id}/revoke
Revocar pasa el grant a Revoked e invalida los tokens asociados.
Solo se revoca lo que estuvo Authorised; lo demás se rechaza o caduca, y llamarlo revocado falsearía el historial que la norma exige auditar.
Lo que Keycloak sí aplica solo
Vale empezar por lo bueno, porque es bastante. Con un perfil de cliente FAPI 2.0 aplicado por política, Keycloak 26.1.4 rechaza correctamente todo esto sin una línea de código propio:
| Intento | Respuesta |
|---|---|
Ir a /auth sin pasar por PAR | 400 Invalid Request |
PKCE con plain | invalid_request |
| Flujo implícito | unauthorized_client |
Reusar un request_uri ya consumido | 400, es de un solo uso |
| PAR sin objeto de petición | invalid_request_object |
| Canjear el código sin certificado de cliente | rechazado |
Eso cubre buena parte del anexo técnico. El objeto de petición cifrado también funciona: el sandbox lo manda como JWE(JWS) con PS256 dentro y RSA-OAEP con A256GCM fuera, y Keycloak lo descifra, verifica la firma y lo procesa.
El orden ahí importa y no es intercambiable. Se firma primero y se cifra después, porque al revés la firma cubriría texto cifrado y no probaría nada sobre el contenido.
Donde el descubrimiento no alcanza a describir al servidor
El primer problema no es un fallo de seguridad ni un incumplimiento. Es que el documento de descubrimiento no logra describir lo que un cliente concreto va a recibir.
require_pushed_authorization_requestsLo que declara el descubrimiento
falseLo que recibe este cliente
Invalid Requestno lo declaraEl campo dice que el servidor acepta también el camino directo, y para este cliente devuelve 400. El RFC 9126 §4.5 permite exigir PAR por cliente; el descubrimiento solo sabe hablar del servidor entero.
code_challenge_methods_supportedLo que declara el descubrimiento
["plain", "S256"]Lo que recibe este cliente
rechaza plainno lo declaraMismo caso: el perfil FAPI que exige S256 se aplica por política de cliente, y la lista publicada es la del servidor.
authorization_details_types_supportedLo que declara el descubrimiento
ausenteLo que recibe este cliente
acepta el RARno lo declaraPublicarlo es MAY en el RFC 9396 §10, así que omitirlo está permitido. El costo es que un cliente no tiene cómo descubrir qué tipos acepta y hay que decírselo por fuera.
Las dos columnas salen del mismo servidor. La izquierda de su .well-known/openid-configuration, la derecha de mandarle la petición que ese documento dice permitir.
La causa es estructural y vale decirla antes de acusar a nadie: esos campos describen al servidor completo, mientras que la exigencia vive en la política del cliente. El propio RFC 9126 §4.5 contempla exigir PAR por cliente, y el descubrimiento no tiene dónde expresar eso. Intenté fijar los campos como atributos del realm y la API respondió 204 sin cambiar nada.
Así que el servidor no miente: el formato no le da forma de decir la verdad cuando la verdad depende de quién pregunte.
El problema práctico igual existe, porque hay clientes que se generan leyendo ese archivo. Una biblioteca que lea require_pushed_authorization_requests: false va a intentar el camino directo, va a recibir un 400 y quien la use va a buscar el error en su propio código.
El hueco de verdad: el RAR entra y no sale
Keycloak acepta authorization_details dentro del objeto de petición. Lo valida, lo guarda en la sesión y no lo emite en el token.
El resultado es un token que autoriza sin decir a qué. El servidor de recursos recibe un permiso genérico y el consentimiento se pierde justo en el tramo donde tenía que servir. No hay un mapeador de fábrica para eso, ni una funcionalidad experimental que lo cubra.
De fábricaLo que sí aplica solo
- PAR obligatorio: sin él, 400
- Objeto de petición firmado y cifrado
- PKCE S256; rechaza plain
- request_uri de un solo uso
- mTLS y token ligado al certificado (cnf.x5t#S256)
- Sin flujo implícito
El huecoLo que hubo que escribir
- Grant Management no existeEs el modelo de consentimiento que la norma eligió, y el producto no lo conoce: no publica grant_management_endpoint ni acuña grantId. Hubo que escribir la API entera y un adaptador delante del PAR.
- El RAR no llega al tokenKeycloak acepta authorization_details en el PAR y lo guarda, pero no lo emite. El servidor de recursos recibe un token que no dice sobre qué se consintió.
- No hay pantalla de consentimiento con el detalle que exige la normaLa pantalla genérica no puede mostrar qué cuentas ni por cuánto tiempo: esos datos están en el banco, no en el servidor de autorización.
1384 líneas propias para que un servidor certificable pase de aceptar el estándar a cumplir la norma de punta a punta. El primero de los tres es el que no se tapa con un mapeador.
Encontrar la causa costó cuatro intentos fallidos. Probé pasar los datos por el parámetro claims, desactivar el token liviano, anidar la estructura, mandarla suelta en el PAR. Todos fallaban igual: el mapeador corría y no encontraba nada.
La respuesta apareció registrando las notas de sesión desde adentro del mapeador. Keycloak prefija los claims del objeto de petición al guardarlos. Lo que busqué durante dos horas como authorization_details estaba ahí todo el tiempo, con este nombre:
private static final String PREFIJO = "client_request_param_";
// El dato existia. Buscarlo por su nombre desnudo no encuentra nada,
// y el mapeador pasa de largo sin quejarse.
String crudo = ctx.getClientSession().getNote(PREFIJO + claim);
De paso apareció el segundo hallazgo, que explica otra tarde perdida: Keycloak descarta los parámetros del objeto de petición que no reconoce. Mandar un consent_id suelto no sirve de nada porque nunca llega al otro lado. El identificador del consentimiento hay que sacarlo de adentro del propio RAR, que sí sobrevive, y que además es donde la norma lo pone.
Hay un detalle de formato que también costó. El valor tiene que emitirse como arreglo de objetos y no como cadena:
// authorization_details es un arreglo. Meterlo como cadena produce un token
// donde el receptor tiene que volver a parsear JSON dentro del JSON.
private Object comoJson(String crudo) {
String t = crudo.trim();
if (t.startsWith("[") || t.startsWith("{")) {
return JsonSerialization.readValue(t, Object.class);
}
return crudo;
}
El consentimiento no lo puede mostrar el servidor de autorización
Este es el punto donde la arquitectura por defecto se rompe, y no por una limitación del producto.
La norma exige mostrarle al cliente final qué se solicita, sobre qué cuentas y por cuánto tiempo antes de que autorice. El servidor de autorización no tiene ninguno de esos tres datos. Los tiene la institución que provee la información, que es quien creó el consentimiento y quien conoce las cuentas. La pantalla genérica de Keycloak puede listar alcances, y un alcance llamado accounts no le dice nada a nadie.
El segundo factor tiene el mismo problema: el código por correo lo manda el banco, con la marca del banco, a la dirección que el cliente registró.
La salida fue un autenticador propio que interrumpe el flujo, redirige al banco con el identificador del consentimiento y una URL de vuelta, y retoma cuando el cliente regresa:
String vuelta = ctx.getActionUrl(ctx.generateAccessCode()).toString();
ctx.challenge(Response.status(302).location(URI.create(destino)).build());
Y en la vuelta, la parte que hace que esto sea una autorización y no un trámite:
if (!"autorizado".equals(resultado)) {
// Dejarlo pasar seria autorizar sin consentimiento.
ctx.getEvent().error("consent_denied");
ctx.failureChallenge(AuthenticationFlowError.ACCESS_DENIED, ...);
return;
}
Colocar ese autenticador en el flujo tiene su propia trampa. Ponerlo como obligatorio en el nivel superior hace que Keycloak ignore todas las alternativas y falle con un mensaje sobre que necesita un usuario. Ponerlo dentro del subflujo de formularios funciona, salvo que haya sesión por cookie, en cuyo caso se salta entero. La única forma de garantizar que la pantalla aparezca siempre es pedir prompt=login desde el objeto de petición.
Seis fallos que no dan error
Este es el patrón que más tiempo me costó y el que justifica escribir el post. Ninguno de estos problemas produce un mensaje que apunte a su causa.
| Síntoma | Causa real |
|---|---|
| El mapeador corre y no emite nada | Los claims están guardados con prefijo client_request_param_ |
| Un parámetro del objeto de petición desaparece | Keycloak descarta los que no reconoce |
| El token sale sin los claims del mapeador | El token liviano está activo y los omite |
| «requires user to be set» | Un obligatorio en el nivel superior anula todas las alternativas |
| La pantalla de consentimiento no aparece | Hay sesión por cookie; falta prompt=login |
| El callback llega vacío | El código de acción caducó: accessCodeLifespan viene en 60 segundos |
El último apareció probando con una persona de verdad en vez de con un navegador automatizado. El flujo sale del servidor de autorización hacia el banco, la persona lee el detalle, pide un código, abre su correo y vuelve. Eso pasa del minuto casi siempre. En pantalla se ve como un callback roto, y solo el registro del servidor dice expired_code.
Hubo un séptimo, que fue mío y no de Keycloak: la pantalla de «autorización completada» del banco ofrecía dos caminos a la vez, un enlace y un temporizador. La URL de acción es de un solo uso, así que la segunda visita volvía como error después de la buena y tapaba la pantalla correcta. La autorización funcionaba y la interfaz decía que había fallado. Lo arreglé quitando la pantalla y devolviendo un 302 desde el servidor.
mTLS detrás de un proxy, que merece su propia advertencia
Keycloak lee el certificado de cliente de una cabecera y su proveedor nginx acepta solo PEM codificado en URL. Caddy 2.11 ofrece el PEM con saltos de línea, que una cabecera HTTP no admite y produce un 502, o el DER en base64, que Keycloak no entiende.
Existe una tercera opción documentada en varios sitios, certificate_pem_urlencoded, que no existe en Caddy. Lo comprobé buscando en el binario. Cuando se usa, Caddy pasa el literal sin resolver y Keycloak responde que no hay certificado de cliente disponible, que parece un fallo de mTLS y no lo es.
La solución fue un puente diminuto que recodifica, cuidando que el + salga como %2B: el decodificador de Java lo convierte en espacio y la verificación falla con un error que tampoco menciona la causa.
Lo que costó
| Pieza | Tamaño |
|---|---|
| Python propio (directorio, banco, grants, adaptadores, solicitante) | 4.594 líneas |
| Java propio (tres clases, proveedores de Keycloak) | 639 líneas |
| Grant Management API | 531 líneas |
| Adaptador del PAR que valida el RAR y acuña el grantId | 214 líneas |
| Construcción y validación del RAR chileno | 193 líneas |
| Verificador contra la norma | 316 líneas |
| Certificados de la PKI (una CA, cuatro participantes) | 13 |
| Subdominios en producción | 6 |
Las 639 líneas de Java son el número que más duele, porque son las que no se pueden evitar con configuración: hay que mantenerlas contra una API interna que el propio Keycloak advierte que puede cambiar sin aviso, y recompilarlas contra la versión exacta del servidor porque un jar de otra no carga.
Pero el número que más sorprende es el otro. La Grant Management API son 531 líneas de un servicio que no existía en ninguna parte, y el adaptador del PAR otras 214. Eso no es rellenar un hueco del producto: es implementar el modelo de consentimiento que la norma eligió.
El error que encontró una pregunta
Alguien me preguntó dónde dice la norma que el IPI tenga que exponer una URL para crear el consentimiento antes del PAR. Fui a buscar la cita y no existe.
En toda la especificación chilena no hay un solo POST /consents. Los únicos endpoints de consentimiento son de gestión de grants:
| Endpoint | Para qué |
|---|---|
GET /grants | Los grants activos del cliente final |
GET /grants/{grant_id} | Detalle y estado |
DELETE o POST /grants/{grant_id}/revoke | Revocar |
GET /grants/history | Historial de eventos, autenticado por mTLS |
Y el orden es el contrario al que había implementado. Cito la especificación de RAR:
El PSBI/PSIP envía la solicitud Pushed Auth Request (
/par) con uno o más objetos dentro delauthorization_details. El Servidor de autorización verifica coherencia entre scope del API y acciones declaradas; si todo es válido, devuelve elrequest_urifirmado.
Y define una respuesta grant con grantId, status: AwaitingAuthorisation y creationDateTime. Es decir: el PAR que lleva el RAR es la creación del consentimiento. No hay llamada aparte, y el identificador lo acuña el servidor de autorización, que pertenece al banco.
Lo que yo había construido era el modelo de Open Banking Brasil. Es el reflejo de cualquiera que haya visto finanzas abiertas antes, y en Chile está equivocado.
Lo que costó arreglarlo
Rehacerlo movió tres cosas que creía resueltas.
El RAR lo redacta el PSBI. La norma le prohíbe expresamente a la IPI alterar el contenido de la solicitud, así que pedirle el authorization_details al banco era dejar que lo redactara él. Ahora lo arma quien pide, que es quien sabe qué servicio va a prestar.
Y no nombra cuentas. Aquí me topé con una contradicción de la propia norma. La tabla del RAR marca identifier como obligatorio, pero la especificación de iniciación de pagos dice del mismo campo: «Opcional. Si no se informa, el Usuario Final seleccionará la cuenta en la interfaz de la Institución Financiera durante la autorización». Y la NCG 569 permite a la IPI ofrecer esa selección. Seguí el comportamiento operativo, porque exigir el identificador obligaría al PSBI a nombrar cuentas que todavía no conoce, y la única forma de conocerlas sería leerlas antes del consentimiento.
Así que la persona elige sus cuentas en el banco, con las casillas sin marcar, que es lo que la norma exige al prohibir opciones premarcadas. Y el servidor poda el grant a lo concedido.
El token lleva lo concedido, no lo pedido. La especificación describe el authorization_details de la respuesta como «echo de lo concedido, verificado y podado por el AS». Emitir la solicitud original daría un token más amplio que el consentimiento.
Lo probé eligiendo distinto dos veces: marcando dos cuentas llegan exactamente esas dos, marcando una sola llega solo esa.
El tercer hueco de Keycloak, que es el mayor
Grant Management no existe en el producto. Su documento de descubrimiento no publica grant_management_endpoint ni grant_management_actions_supported; el único campo con la palabra es grant_types_supported, que es otra cosa.
Así que a los dos proveedores hubo que sumarles un servicio con la API completa, un adaptador delante del PAR que valida el RAR chileno y acuña el grantId, y un cliente en Java para que el autenticador mueva el grant a Authorised cuando la persona firma.
Lo que encontró revisar la norma línea por línea
Escribí un verificador que lee los requisitos y los comprueba contra el sandbox vivo, guardando lo que devuelve. Un punto sin evidencia sale como «sin comprobar», nunca como cumplido.
Encontró un incumplimiento que llevaba ahí desde el principio: el perfil de seguridad exige PS256 y el realm publicaba solo RS256. Quien verificara firmas como describe la norma no habría encontrado con qué. Keycloak firma en RS256 de fábrica y hay que decírselo en tres sitios: la clave del realm, el algoritmo por defecto y los atributos del cliente.
Y destapó una segunda contradicción de la norma. El perfil de seguridad dice que purpose es «un campo informativo que no deberá ser usado para ninguna restricción», mientras las reglas de validación de la CMF exigen que su valor esté en la lista publicada. Lo resolví validando el valor y no usándolo para decidir accesos: quien decide es el grant.
Un detalle sobre el propio verificador: la primera versión reportaba «no cumple» cuando le faltaba el certificado de cliente. No poder comprobar algo no es incumplirlo, y confundir las dos cosas convierte un checklist en ruido.
Cuándo sí y cuándo no
Keycloak sirve si se está construyendo un participante y se necesita algo funcionando esta semana. Aplica la mayor parte del perfil por configuración, es gratis, se autoaloja y los dos huecos están identificados con nombre y archivo en este post.
Keycloak no basta solo. Con los proveedores puestos el flujo cumple y el token sale con su RAR y su grantId; sin ellos, no. Quien planifique una implementación tiene que presupuestar ese código y su mantención, no darlo por incluido.
Y esto es un sandbox de desarrollo, nunca productivo. Sirve para probar una implementación contra un sistema que se comporta como manda la norma en el cable. No sustituye a ATENA, no opera, y las partes que faltan son justo las que separan una demostración de un participante: retención regulatoria, propagación entre paneles y el ciclo de vida completo del consentimiento. Eso va en la implementación siguiente, que es otro post. Quien planifique una implementación tiene que presupuestar ese código y su mantención, no darlo por incluido.
Y hay una asimetría que conviene decir en voz alta: el hueco está del lado del consentimiento, que es justo la parte que la norma protege. Lo que Keycloak aplica bien es la criptografía y el transporte. Lo que hay que escribir a mano es lo que le explica a una persona sobre qué está autorizando.
Lo que viene
La siguiente versión del sandbox va sobre WSO2 Identity Server, y esa es la promesa con la que cierro. La razón es exactamente lo que este post documenta: WSO2 trae el soporte de Rich Authorization Requests como funcionalidad de producto, con los tipos de detalle registrados por API en vez de deducidos de una nota de sesión con prefijo, y documenta la creación de aplicaciones FAPI 2.0 desde su consola.
Si eso significa que los dos proveedores propios sobran, el resultado de la comparación es el contenido del próximo post. Si no sobran, también.
Fuentes
- CMF · Norma que regula el Sistema de Finanzas Abiertas, Comisión para el Mercado Financiero. La NCG N°514 y su alcance.
- CMF · Anexo Técnico del SFA, donde se define el perfil de seguridad que este sandbox implementa.
- Portal del desarrollador de Open Finance Chile, con las especificaciones de las APIs y la colección de Postman.
- RFC 9126 · OAuth 2.0 Pushed Authorization Requests. El PAR que el anexo hace obligatorio.
- RFC 9396 · OAuth 2.0 Rich Authorization Requests. La §10 es la que pide publicar los tipos soportados.
- RFC 8705 · Mutual-TLS Client Authentication and Certificate-Bound Access Tokens. El
cnf.x5t#S256que hace inútil un token robado. - RFC 7591 · OAuth 2.0 Dynamic Client Registration, base del registro contra la declaración de software del Directorio.
- FAPI 2.0 Security Profile, OpenID Foundation.
- WSO2 Identity Server · Rich Authorization Requests, el soporte nativo que motiva la siguiente versión.
Comentarios
Todavía no hay comentarios. El primero es tuyo.