# QWAC vs QSealC: quién hace qué, y por qué se equivoca de capa

> El QWAC lo lee la capa TLS, una vez, en el handshake. El QSealC lo lee la capa aplicativa, en cada petición. Dónde vive cada clave privada, qué exige STET y cómo leer un «error de certificado».

_Publicado el: 2026-08-24 · Category: seguridad · Tags: #dsp2 #psd2 #open-banking #seguridad #eidas #mtls #stet_

---

Los dos certificados salen del mismo [eIDAS TSP](https://openfinanceguide.com/es/glossary/eidas-tsp), llevan el mismo número de autorización y los mismos roles [DSP2](https://openfinanceguide.com/es/glossary/dsp2), y a menudo llegan en el mismo pedido. La diferencia no está en lo que contienen. Está en **la capa de la pila que los lee**. El [QWAC](https://openfinanceguide.com/es/glossary/qwac) lo lee la capa TLS, una vez, en el handshake. El [QSealC](https://openfinanceguide.com/es/glossary/qsealc) lo lee la capa aplicativa, en cada petición, en una cabecera HTTP.

Un ticket que dice «error de certificado» sin decir en qué capa no es diagnosticable. Este artículo no redefine los dos objetos — las fichas QWAC, QSealC y [mTLS](https://openfinanceguide.com/es/glossary/mtls) ya lo hacen. Sitúa cada uno en la pila, en la infraestructura y en los logs, con el texto de [STET](https://openfinanceguide.com/es/glossary/stet) 1.6.3 en paralelo. Para el panorama completo del stack, lea antes [Arquitectura técnica de una API DSP2](/es/blog/architecture-api-dsp2).

## En una imagen: dos capas, dos certificados

Una petición DSP2 baja por la pila. Cada certificado interviene en un solo nivel.

```mermaid
flowchart LR
    subgraph APP["Capa aplicativa"]
        BIZ["Lógica de negocio del TPP\nGET /accounts\nPOST /payment-requests"]
    end
    subgraph MSG["Capa de mensaje HTTP — en cada petición"]
        SIG["Digest + Signature\no x-jws-signature\n🔏 clave privada del QSealC"]
        TOK["Authorization: Bearer …\ntoken vinculado al certificado\nde cliente (RFC 8705)"]
    end
    subgraph TLS["Capa de transporte — por conexión"]
        MT["Handshake mTLS, TLS 1.2\n🪪 QWAC del TPP\n⇄ QWAC del ASPSP"]
    end
    APP --> MSG --> TLS -->|"Internet"| ASPSP["ASPSP"]
```

| | QWAC | QSealC |
|---|---|---|
| Base eIDAS | certificado cualificado de autenticación de sitio web, art. 3(39) | certificado cualificado de sello electrónico, art. 3(30) |
| Capa | transporte (TLS) | mensaje (HTTP) |
| Leído por | la pila TLS de ambos lados, en el handshake | el verificador de firma del [ASPSP](https://openfinanceguide.com/es/glossary/aspsp), en cada petición |
| Frecuencia | una vez por conexión | una vez por petición — y por respuesta si el ASPSP firma |
| Sobrevive a un proxy que termina TLS | no | sí |
| Valor probatorio a posteriori | ninguno: no se conserva nada | la firma queda en la petición, verificable más tarde |
| Clave privada, lado [TPP](https://openfinanceguide.com/es/glossary/tpp) | donde se abre la conexión saliente | donde se construye la petición |
| Síntoma cuando falla | handshake roto, ningún código HTTP | HTTP 400 (STET §3.5) |
| Lo que dice STET | §3.1, §3.2, §3.9 | §3.5 |

> **Info:**
> La RTS (UE) 2018/389, art. 34, admite uno u otro para la identificación. Los
>   estándares de API zanjaron la cuestión apilándolos: STET y Berlin Group exigen el QWAC
>   para el canal y el QSealC para la firma. Dos certificados, por tanto dos claves
>   privadas, dos fechas de expiración y dos procedimientos de rotación.

## Lo que exige STET, texto en mano

Bastan tres pasajes del Framework 1.6.3 (Part 1). Las páginas son las del PDF oficial, que el manual muestra al margen de cada párrafo.

### §3.1 y §3.2 — el QWAC, para TLS y nada más

«Each actor must be provided with at least one eIDAS certificate (QWAC), for TLS 1.2 purpose» ([§3.1, p. 17](/es/stet/1.6.3/framework/3-prerequisites-and-technical-details#s-3.1)). El TPP verifica el QWAC de servidor del ASPSP y presenta el suyo; ambos respetan ETSI TS 119 495; «in case of authentication failure, on one side or the other, the connection must be closed»; y «no additional encrypting or authenticating feature is required» ([§3.2, p. 17–18](/es/stet/1.6.3/framework/3-prerequisites-and-technical-details#s-3.2)). El QWAC nunca sube hasta la petición: todo lo que tiene que decir, lo dice en el handshake.

El mismo párrafo fija el formato del `organizationIdentifier` del sujeto del certificado: `PSD`, el código de país de la [NCA](https://openfinanceguide.com/es/glossary/nca), un guion, el identificador de la NCA, un guion, el número de autorización — `PSDFR-ACPR-12345` para un actor autorizado por la [ACPR](https://openfinanceguide.com/es/glossary/acpr). Es ese campo, junto con los roles del QC statement ETSI, lo que el ASPSP lee en el certificado de cliente para saber quién llama, y en calidad de qué.

### §3.5 — el QSealC, para firmar cada petición

«Each request sent by the TPP has to be signed and ASPSP might also sign their responses. If the ASPSP notes that the signature is either absent or invalid for a given request, it shall reject this request with HTTP400» ([§3.5, p. 53](/es/stet/1.6.3/framework/3-prerequisites-and-technical-details#s-3.5)).

El mecanismo histórico es [HTTP Signature](https://openfinanceguide.com/es/glossary/http-signature) en su versión draft-cavage: un `Digest` SHA-256 del cuerpo, y luego una firma RSA-SHA256 hecha con el QSealC sobre `(request-target)`, `Date`, `Content-Type`, `Content-Length`, `X-Request-Id`, las cabeceras `PSU-*` y el propio `Digest`, transportada en la cabecera `Signature` con `keyId`, `algorithm` y la lista de cabeceras firmadas ([§3.5.1.1, p. 53–55](/es/stet/1.6.3/framework/3-prerequisites-and-technical-details#s-3.5.1.1)). El `keyId` es o bien el identificador asignado durante el registro [OAuth2](https://openfinanceguide.com/es/glossary/oauth2), o bien la URL del QSealC sufijada con un `_` y su huella SHA-256 ([§3.5.1.2, p. 55](/es/stet/1.6.3/framework/3-prerequisites-and-technical-details#s-3.5.1.2)).

Desde la 1.6.3, el Framework aconseja sustituir este mecanismo por el perfil JSON Web Signature de Open Banking Europe: un JWS separado (RFC 7515) en la cabecera `x-jws-signature`, cuya cabecera protegida lleva `x5t#S256`, la huella del QSealC firmante, y `sigD`, la lista de cabeceras cubiertas ([§3.5.2, p. 55–56](/es/stet/1.6.3/framework/3-prerequisites-and-technical-details#s-3.5.2)). El draft-cavage, por su parte, ha sido reemplazado en el IETF por la RFC 9421 — que STET no cita como objetivo.

### §3.9 — el QWAC vuelve una tercera vez, en OAuth2

«Based on MTLS, the identity of the TPP is provided by its eIDAS certificate during OAuth2 procedures», con remisión a la RFC 8705 ([§3.9, p. 59](/es/stet/1.6.3/framework/3-prerequisites-and-technical-details#s-3.9)). El servidor de autorización autentica por tanto al cliente mediante `tls_client_auth` — el QWAC presentado en el handshake del `POST /token` — y puede vincular el token emitido a ese certificado, a través de su claim `cnf` y la huella `x5t#S256`. Un token vinculado a una huella que ya no es la presentada se rechaza, aunque no haya expirado.

## Una petición, tres verificaciones

```mermaid
sequenceDiagram
    autonumber
    participant TPP as TPP (cliente HTTP)
    participant TLS as ASPSP — terminación TLS
    participant AS as ASPSP — servidor de autorización
    participant API as ASPSP — API y verificador de firma

    TPP->>TLS: ClientHello
    TLS-->>TPP: Certificate (QWAC del ASPSP) + CertificateRequest
    TPP->>TLS: Certificate (QWAC del TPP) + CertificateVerify
    alt QWAC desconocido, expirado, revocado, sin el rol
        TLS-->>TPP: alert TLS — conexión cerrada, ningún código HTTP
    end
    TLS-->>TPP: Finished — canal mTLS establecido

    TPP->>AS: POST /token (tls_client_auth, por el canal mTLS)
    AS-->>TPP: access_token vinculado al QWAC (cnf.x5t#35;S256)

    TPP->>API: GET /accounts<br/>Authorization: Bearer …<br/>Digest + Signature (QSealC)
    API->>API: ¿token válido, vinculado a este certificado de cliente?
    alt token ausente, expirado, vinculado a otro certificado
        API-->>TPP: 401
    end
    API->>API: ¿Digest recalculado, Signature verificada con el QSealC del keyId?
    alt firma ausente o inválida
        API-->>TPP: 400 (STET §3.5)
    end
    API-->>TPP: 200 + cuerpo, firmado por el ASPSP si procede
```

Tres verificaciones, tres capas, tres síntomas que no se parecen en nada. El orden de las dos verificaciones aplicativas es propio de cada banco; el síntoma, en cambio, no cambia.

## Dónde viven las claves privadas: el error de capa

Aquí es donde uno se equivoca. Las dos claves privadas no viven en el mismo sitio, porque los dos certificados no los lee el mismo componente.

### Del lado del TPP, en salida

```mermaid
flowchart LR
    subgraph TPP["Infraestructura del TPP"]
        APP["Servicio aplicativo\nconstruye la petición"]
        SIGN["Servicio de firma\n🔏 clave privada del QSealC (HSM / KMS)"]
        EGRESS["Cliente HTTP o proxy saliente\n🪪 clave privada del QWAC\nabre la conexión mTLS"]
        APP -->|"petición + Digest"| SIGN
        SIGN -->|"petición + Signature"| EGRESS
    end
    EGRESS -->|"mTLS"| BANK["ASPSP"]
```

La clave del QSealC pertenece al componente que **construye la petición**, porque la firma cubre cabeceras — `Date`, `Content-Length`, `X-Request-Id` — que deben quedar fijadas antes del envío. La clave del QWAC pertenece al componente que **abre la conexión**. Si un proxy saliente, un NAT aplicativo o un service mesh abre las conexiones en lugar de la aplicación, es él quien presenta el QWAC, y la aplicación no tiene por qué guardar su clave. Poner la clave del QWAC en la aplicación cuando el mesh termina y reabre TLS da un handshake sin certificado de cliente: una alerta TLS, y ningún código HTTP.

### Del lado del ASPSP, en entrada

```mermaid
flowchart LR
    TPP["TPP"] -->|"mTLS"| LB
    subgraph ASPSP["Infraestructura del ASPSP"]
        LB["Terminación TLS — LB, WAF, gateway\npide y verifica el QWAC de cliente\n🪪 clave privada del QWAC de servidor"]
        GW["Verificador de firma\nrecalcula Digest, resuelve keyId,\nverifica con el QSealC del TPP"]
        AUTHZ["Autorización\ntoken, consentimiento, rol"]
        BE["Backend de negocio"]
        LB -->|"petición + identidad extraída del QWAC\n(cabecera interna, firmada)"| GW
        GW --> AUTHZ --> BE
    end
```

Del lado del banco, el QWAC de cliente se detiene en el componente que termina TLS. Lo que pasa después es una **identidad extraída** — `organizationIdentifier`, roles, huella — transmitida en una cabecera interna que el resto de la cadena tiene que poder creer, de ahí el interés de firmarla o de aceptarla solo del terminador. La firma QSealC, en cambio, lo atraviesa todo: un componente situado en cualquier punto por detrás puede verificarla, **a condición de ver los bytes originales**. Una gateway que reserializa el JSON, recalcula `Content-Length` o normaliza `Date` antes del verificador produce 400 en peticiones válidas.

### Las seis confusiones de capa

| # | Confusión | Qué ocurre | Síntoma |
|---|---|---|---|
| 1 | Clave del QWAC en un componente que no abre la conexión | handshake sin certificado de cliente | alerta TLS, sin HTTP |
| 2 | QSealC presentado como certificado de cliente TLS, o QWAC usado para firmar | el `QcType` ETSI no es el correcto (`web` para uno, `eseal` para el otro); un verificador que lo controle rechaza | alerta TLS, o 400 |
| 3 | Firma calculada antes de un proxy que reescribe `Date`, `Content-Length` o el cuerpo | la firma cubre otros bytes que los recibidos | 400, firma inválida |
| 4 | Verificación hecha después de una transformación de la petición, del lado del ASPSP | misma causa, en espejo | 400 en peticiones válidas |
| 5 | QWAC renovado sin reemitir los tokens vinculados | `cnf.x5t#S256` ya no corresponde al certificado presentado | 401 con un token no expirado |
| 6 | CA del QTSP del banco ausente del almacén de confianza del TPP | el TPP rechaza el QWAC de servidor del ASPSP | `unknown_ca`, conexión cerrada por el propio TPP |

> **Warning:**
> La confusión 2 es la más costosa, porque es invisible mientras el banco no controle
>   el tipo de certificado — y visible de golpe el día en que lo haga. Los dos
>   certificados se venden a menudo juntos; no son intercambiables.

## Matriz de diagnóstico

| Síntoma | Capa | Certificado implicado | Dónde mirar |
|---|---|---|---|
| Conexión cerrada durante el handshake, ningún código HTTP; `handshake_failure`, `bad_certificate`, `certificate_expired`, `unknown_ca` | TLS | QWAC — el suyo, o el del banco que usted no reconoce | logs del terminador TLS; `openssl s_client -cert -key` |
| 401 | token | QWAC, vía el binding RFC 8705; o simplemente un token expirado | respuesta del servidor de autorización; `cnf` del token frente a la huella del certificado presentado |
| 400 que menciona la firma (STET §3.5) | mensaje HTTP | QSealC | `Digest` recalculado; lista de cabeceras firmadas; `keyId` resoluble; bytes realmente enviados |
| 403, o código de negocio del banco | autorización | ninguno de los dos | roles PSD2 del certificado frente al endpoint llamado; estado del consentimiento |

```mermaid
flowchart TD
    Q0["«Error de certificado»"] --> Q1{"¿Ha vuelto\nun código HTTP?"}
    Q1 -->|"No"| TLSX["Capa TLS → QWAC\ncadena, revocación, rol,\nalmacén de confianza, quién abre la conexión"]
    Q1 -->|"Sí"| Q2{"¿Cuál?"}
    Q2 -->|"400"| SIGX["Capa de mensaje → QSealC\nDigest, cabeceras firmadas, keyId,\nbytes reescritos por el camino"]
    Q2 -->|"401"| TOKX["Capa token → binding RFC 8705\n¿está el token vinculado al certificado presentado?"]
    Q2 -->|"403 u otro"| ROLEX["Autorización → ninguno de los dos\nrol PSD2, consentimiento, perímetro"]
```

## Dos relojes

Los dos certificados expiran cada uno en su fecha, son revocables cada uno por su lado y se renuevan por separado. Tres consecuencias:

- **Dos alertas de expiración**, no una. Un QWAC válido no dice nada del estado del QSealC, y a la inversa.
- **La rotación del QWAC afecta a los tokens.** El registro OAuth2 sobrevive si el DN del sujeto no cambia — es el que declara `tls_client_auth_subject_dn` — pero los access tokens vinculados a la huella antigua (RFC 8705) ya no pasan. Reemitir los tokens forma parte de la rotación.
- **La rotación del QSealC afecta al `keyId`** — cambia la URL sufijada con la huella, o el `x5t#S256` del perfil JWS — y no debe retirar el certificado antiguo: las peticiones ya firmadas deben seguir siendo verificables. Es todo el sentido del no repudio. Se retira la clave privada, se deja el certificado publicado.

```mermaid
gantt
    title Dos certificados, dos calendarios
    dateFormat YYYY-MM-DD
    axisFormat %m/%Y
    section QWAC
    QWAC n                                  :done, q1, 2026-01-01, 2027-01-01
    QWAC n+1                                :active, q2, 2026-11-15, 2027-11-15
    Reemisión de los tokens vinculados      :crit, milestone, m1, 2026-11-15, 0d
    section QSealC
    QSealC n                                :done, s1, 2026-04-01, 2027-04-01
    QSealC n+1                              :active, s2, 2027-02-15, 2028-02-15
    QSealC antiguo aún publicado            :s3, 2027-04-01, 2027-10-01
```

## Para resumir

1. **QWAC = capa TLS.** Se lee en el handshake, por el componente que abre o termina la conexión. Su clave vive ahí, y en ningún otro sitio.
2. **QSealC = capa de mensaje.** Se lee en cada petición, por un verificador que debe ver los bytes originales. Su clave vive donde se construye la petición.
3. **STET**: QWAC para TLS 1.2 (§3.1–3.2), cada petición firmada con el QSealC y 400 en caso contrario (§3.5), perfil JWS recomendado desde la 1.6.3 (§3.5.2), identidad OAuth2 aportada por el QWAC (§3.9, RFC 8705).
4. **Sin código HTTP → QWAC. 400 → QSealC. 401 → binding del token. 403 → ninguno de los dos.**
5. **Dos relojes**: dos expiraciones, dos rotaciones, y el QSealC antiguo sigue publicado para verificar el pasado.

Para el resto del stack — estándares, flujos [SCA](https://openfinanceguide.com/es/glossary/sca), consentimiento — véase [Arquitectura técnica de una API DSP2](/es/blog/architecture-api-dsp2). Los dos mecanismos de firma, byte a byte, y los fallos de handshake más frecuentes son objeto de los dos artículos siguientes. El texto íntegro de la sección citada está en el [manual STET 1.6.3](/es/stet/1.6.3/framework/3-prerequisites-and-technical-details), con el PDF original en paralelo.

---

Fuente: https://openfinanceguide.com/es/blog/qwac-vs-qsealc
